diff --git a/e2e/tests/plugin-builder-website.spec.ts b/e2e/tests/plugin-builder-website.spec.ts new file mode 100644 index 000000000..d9cc0df98 --- /dev/null +++ b/e2e/tests/plugin-builder-website.spec.ts @@ -0,0 +1,38 @@ +import { expect, test } from '@playwright/test' + +// Smoke test for the drag-and-drop website builder plugin. Loads the seeded +// example page, verifies the editor mounts, then switches to Preview and +// verifies the published site renders without the editing chrome. +test.beforeEach(async ({ page }) => { + await page.goto( + 'http://localhost:3000/view/?documentId=dmss://DemoDataSource/$builderPageExample' + ) +}) + +test('mounts the builder editor with its toolbar and palette', async ({ + page, +}) => { + await expect(page.getByText('Website builder')).toBeVisible() + // The widget palette is only present in edit mode. + await expect(page.getByText('Widgets')).toBeVisible() + // The pages sidebar is present with an add-page affordance. + await expect(page.getByRole('button', { name: 'Add page' })).toBeVisible() +}) + +test('switches to the read-only preview and hides the editing chrome', async ({ + page, +}) => { + await expect(page.getByText('Widgets')).toBeVisible() + + await page.getByRole('button', { name: 'Preview', exact: true }).click() + + // Palette and page-editing affordances disappear in preview. + await expect(page.getByText('Widgets')).toBeHidden() + await expect(page.getByRole('button', { name: 'Add page' })).toBeHidden() + // The published navigation menu renders instead. + await expect(page.getByText('Menu')).toBeVisible() + + // Returning to edit restores the palette. + await page.getByRole('button', { name: 'Edit', exact: true }).click() + await expect(page.getByText('Widgets')).toBeVisible() +}) diff --git a/e2e/tests/plugin-form-dimensional_scalar.spec.ts b/e2e/tests/plugin-form-dimensional_scalar.spec.ts index f593419fd..fab00a8ca 100644 --- a/e2e/tests/plugin-form-dimensional_scalar.spec.ts +++ b/e2e/tests/plugin-form-dimensional_scalar.spec.ts @@ -63,7 +63,7 @@ test('Dimensional scalar', async ({ page }) => { .getByLabel('label (optional)') .fill('Should not show as config overrides') await page.getByRole('button', { name: 'Submit' }).click() - await expect(page.getByRole('alert')).not.toBeVisible() + await expect(page.locator('.Toastify__toast--error')).not.toBeVisible() await page.getByRole('button', { name: 'waveForm' }).click() await expect(page.getByText('significantWaveHeight (config)')).toBeVisible() await expect( @@ -87,8 +87,8 @@ test('Dimensional scalar', async ({ page }) => { await page.getByLabel('label (optional)').fill('New Maximum') await page.getByLabel('unit (optional)').fill('€') await page.getByRole('button', { name: 'Submit' }).click() - await expect(page.getByRole('alert')).not.toBeVisible() - await page.getByRole('button', { name: 'waveForm' }).click() + await expect(page.locator('.Toastify__toast--error')).not.toBeVisible() + await navigate() await expect( page.getByTestId('maximumWaveHeight').getByRole('spinbutton') ).toHaveValue('88888') diff --git a/example/.env.local b/example/.env.local index cb3b43eaf..97d02ce81 100644 --- a/example/.env.local +++ b/example/.env.local @@ -1,4 +1,5 @@ VITE_REDIRECT_URI=http://localhost:3000/ VITE_DMSS_URL=http://localhost:5000 VITE_DM_JOB_URL=http://localhost:5001 -VITE_BLUEPRINT_CACHE_ENABLED=0 \ No newline at end of file +VITE_BLUEPRINT_CACHE_ENABLED=0 +VITE_AUTH_ENABLED=0 \ No newline at end of file diff --git a/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Member.blueprint.json b/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Member.blueprint.json new file mode 100644 index 000000000..e761497d2 --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Member.blueprint.json @@ -0,0 +1,22 @@ +{ + "name": "Member", + "type": "dmss://system/SIMOS/Blueprint", + "attributes": [ + { + "name": "type", + "type": "dmss://system/SIMOS/BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "name", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "role", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + } + ] +} diff --git a/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Page.blueprint.json b/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Page.blueprint.json new file mode 100644 index 000000000..cfd8f533e --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/example/blueprints/Page.blueprint.json @@ -0,0 +1,48 @@ +{ + "name": "Page", + "type": "dmss://system/SIMOS/Blueprint", + "attributes": [ + { + "name": "type", + "type": "dmss://system/SIMOS/BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "name", + "type": "dmss://system/SIMOS/BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "title", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + }, + { + "name": "layout", + "type": "CORE:BlueprintAttribute", + "attributeType": "object", + "optional": true + }, + { + "name": "cover", + "type": "CORE:BlueprintAttribute", + "attributeType": "CORE:File", + "contained": false, + "optional": true + }, + { + "name": "contact", + "type": "CORE:BlueprintAttribute", + "attributeType": "./Member", + "optional": true + }, + { + "name": "team", + "type": "CORE:BlueprintAttribute", + "attributeType": "./Member", + "dimensions": "*", + "optional": true + } + ] +} diff --git a/example/app/data/DemoDataSource/plugins/builder/example/pageExample.entity.json b/example/app/data/DemoDataSource/plugins/builder/example/pageExample.entity.json new file mode 100644 index 000000000..59c2ab22b --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/example/pageExample.entity.json @@ -0,0 +1,28 @@ +{ + "_id": "builderPageExample", + "name": "BuilderPageExample", + "type": "./blueprints/Page", + "title": "My first page", + "cover": { + "type": "dmss://system/SIMOS/Reference", + "address": "../../media_viewer/assets/beauty", + "referenceType": "link" + }, + "contact": { + "type": "./blueprints/Member", + "name": "Ada Lovelace", + "role": "Owner" + }, + "team": [ + { + "type": "./blueprints/Member", + "name": "Ada Lovelace", + "role": "Owner" + }, + { + "type": "./blueprints/Member", + "name": "Alan Turing", + "role": "Engineer" + } + ] +} diff --git a/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/SiteDirectory.blueprint.json b/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/SiteDirectory.blueprint.json new file mode 100644 index 000000000..6de98aee9 --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/SiteDirectory.blueprint.json @@ -0,0 +1,7 @@ +{ + "name": "SiteDirectory", + "type": "dmss://system/SIMOS/Blueprint", + "description": "A landing entity whose recipe mounts the Site directory plugin: a gallery of saved websites.", + "extends": ["CORE:NamedEntity"], + "attributes": [] +} diff --git a/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/Website.blueprint.json b/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/Website.blueprint.json new file mode 100644 index 000000000..5ab9dc748 --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/sites/blueprints/Website.blueprint.json @@ -0,0 +1,15 @@ +{ + "name": "Website", + "type": "dmss://system/SIMOS/Blueprint", + "description": "A saved website built with the drag-and-drop builder. Extends the builder Site (navbar + pages) and adds a human-friendly name shown in the site directory.", + "extends": ["PLUGINS:dm-core-plugins/builder/Site"], + "attributes": [ + { + "name": "name", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Display name of the website, shown as the card title in the directory.", + "optional": true + } + ] +} diff --git a/example/app/data/DemoDataSource/plugins/builder/sites/siteDirectory.entity.json b/example/app/data/DemoDataSource/plugins/builder/sites/siteDirectory.entity.json new file mode 100644 index 000000000..18ed4f27d --- /dev/null +++ b/example/app/data/DemoDataSource/plugins/builder/sites/siteDirectory.entity.json @@ -0,0 +1,5 @@ +{ + "_id": "siteDirectory", + "name": "SiteDirectory", + "type": "./blueprints/SiteDirectory" +} diff --git a/example/app/data/DemoDataSource/recipes/plugins/builder/example/package.json b/example/app/data/DemoDataSource/recipes/plugins/builder/example/package.json new file mode 100644 index 000000000..c358e927d --- /dev/null +++ b/example/app/data/DemoDataSource/recipes/plugins/builder/example/package.json @@ -0,0 +1,9 @@ +{ + "name": "example", + "type": "CORE:Package", + "isRoot": false, + "_meta_": { + "type": "CORE:Meta", + "version": "0.0.1" + } +} diff --git a/example/app/data/DemoDataSource/recipes/plugins/builder/example/page.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/builder/example/page.recipe.json new file mode 100644 index 000000000..459936292 --- /dev/null +++ b/example/app/data/DemoDataSource/recipes/plugins/builder/example/page.recipe.json @@ -0,0 +1,118 @@ +{ + "type": "CORE:RecipeLink", + "_blueprintPath_": "/plugins/builder/example/blueprints/Page", + "initialUiRecipe": { + "name": "Builder", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/builder", + "config": { + "type": "PLUGINS:dm-core-plugins/builder/BuilderPluginConfig", + "mode": "edit", + "initialConfig": { + "type": "PLUGINS:dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "PLUGINS:dm-core-plugins/grid/GridSize", + "columns": 12, + "rows": 8, + "rowGap": "16px", + "columnGap": "16px" + }, + "items": [ + { + "type": "PLUGINS:dm-core-plugins/grid/GridItem", + "title": "Heading", + "gridArea": { + "type": "PLUGINS:dm-core-plugins/grid/GridArea", + "rowStart": 1, + "columnStart": 1, + "rowEnd": 1, + "columnEnd": 12 + }, + "viewConfig": { + "type": "CORE:InlineRecipeViewConfig", + "recipe": { + "name": "form", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/form" + } + } + }, + { + "type": "PLUGINS:dm-core-plugins/grid/GridItem", + "title": "Section", + "gridArea": { + "type": "PLUGINS:dm-core-plugins/grid/GridArea", + "rowStart": 2, + "columnStart": 1, + "rowEnd": 6, + "columnEnd": 6 + }, + "viewConfig": { + "type": "CORE:InlineRecipeViewConfig", + "recipe": { + "name": "Section", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/grid", + "config": { + "type": "PLUGINS:dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "PLUGINS:dm-core-plugins/grid/GridSize", + "columns": 6, + "rows": 4, + "rowGap": "16px", + "columnGap": "16px" + }, + "items": [ + { + "type": "PLUGINS:dm-core-plugins/grid/GridItem", + "title": "Edit page", + "gridArea": { + "type": "PLUGINS:dm-core-plugins/grid/GridArea", + "rowStart": 1, + "columnStart": 1, + "rowEnd": 3, + "columnEnd": 6 + }, + "viewConfig": { + "type": "CORE:InlineRecipeViewConfig", + "recipe": { + "name": "form", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/form" + } + } + } + ], + "itemBorder": { + "type": "PLUGINS:dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + } + } + } + } + ], + "itemBorder": { + "type": "PLUGINS:dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + } + } + }, + "uiRecipes": [ + { + "name": "Edit", + "type": "CORE:UiRecipe", + "category": "edit", + "plugin": "@development-framework/dm-core-plugins/form" + } + ] +} diff --git a/example/app/data/DemoDataSource/recipes/plugins/builder/sites/directory.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/directory.recipe.json new file mode 100644 index 000000000..46599075a --- /dev/null +++ b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/directory.recipe.json @@ -0,0 +1,20 @@ +{ + "type": "CORE:RecipeLink", + "_blueprintPath_": "/plugins/builder/sites/blueprints/SiteDirectory", + "initialUiRecipe": { + "name": "Directory", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/builder/directory", + "config": { + "type": "PLUGINS:dm-core-plugins/builder/SiteDirectoryConfig", + "heading": "Websites", + "entityType": "dmss://DemoDataSource/plugins/builder/sites/blueprints/Website", + "dataSources": ["Sites"], + "targetDataSource": "Sites", + "viewUrlTemplate": "/view?documentId=dmss://{dataSource}/${id}", + "newSiteUrlTemplate": "/view?documentId=dmss://{dataSource}/${id}&recipe=Edit", + "newSiteName": "New_site", + "allowCreate": true + } + } +} diff --git a/example/app/data/DemoDataSource/recipes/plugins/builder/sites/package.json b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/package.json new file mode 100644 index 000000000..fdadb34cc --- /dev/null +++ b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/package.json @@ -0,0 +1,9 @@ +{ + "name": "sites", + "type": "CORE:Package", + "isRoot": false, + "_meta_": { + "type": "CORE:Meta", + "version": "0.0.1" + } +} diff --git a/example/app/data/DemoDataSource/recipes/plugins/builder/sites/website.recipe.json b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/website.recipe.json new file mode 100644 index 000000000..87916a4ef --- /dev/null +++ b/example/app/data/DemoDataSource/recipes/plugins/builder/sites/website.recipe.json @@ -0,0 +1,26 @@ +{ + "type": "CORE:RecipeLink", + "_blueprintPath_": "/plugins/builder/sites/blueprints/Website", + "initialUiRecipe": { + "name": "View", + "type": "CORE:UiRecipe", + "plugin": "@development-framework/dm-core-plugins/builder", + "config": { + "type": "PLUGINS:dm-core-plugins/builder/BuilderPluginConfig", + "readOnly": true + } + }, + "uiRecipes": [ + { + "name": "Edit", + "type": "CORE:UiRecipe", + "category": "edit", + "plugin": "@development-framework/dm-core-plugins/builder", + "config": { + "type": "PLUGINS:dm-core-plugins/builder/BuilderPluginConfig", + "mode": "edit", + "directoryUrl": "/view?documentId=dmss://DemoDataSource/$siteDirectory" + } + } + ] +} diff --git a/example/app/data_source_templates/Sites.json b/example/app/data_source_templates/Sites.json new file mode 100644 index 000000000..faa23a489 --- /dev/null +++ b/example/app/data_source_templates/Sites.json @@ -0,0 +1,16 @@ +{ + "name": "Sites", + "repositories": { + "sitesRepo": { + "type": "mongo-db", + "collection": "default", + "data_types": ["blob"], + "database": "Sites", + "host": "db", + "password": "maf", + "port": 27017, + "tls": false, + "username": "maf" + } + } +} diff --git a/example/app/data_source_templates_radix_demo/Sites.json b/example/app/data_source_templates_radix_demo/Sites.json new file mode 100644 index 000000000..5bddd99c4 --- /dev/null +++ b/example/app/data_source_templates_radix_demo/Sites.json @@ -0,0 +1,16 @@ +{ + "name": "Sites", + "repositories": { + "sitesRepo": { + "type": "mongo-db", + "collection": "default", + "data_types": ["blob"], + "database": "Sites", + "host": "db", + "password": "$MONGO_PASSWORD", + "port": 27017, + "tls": false, + "username": "maf" + } + } +} diff --git a/example/app/demo_sites/acmeMarketing.json b/example/app/demo_sites/acmeMarketing.json new file mode 100644 index 000000000..e2349d65f --- /dev/null +++ b/example/app/demo_sites/acmeMarketing.json @@ -0,0 +1,185 @@ +{ + "_id": "demo-site-acme-marketing", + "type": "dmss://DemoDataSource/plugins/builder/sites/blueprints/Website", + "schemaVersion": 1, + "navbar": { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Navbar", + "enabled": true, + "brand": "Acme", + "background": "#ffffff", + "color": "#3d3d3d", + "brandColor": "#007079", + "align": "right", + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/NavbarItem", + "id": "nav-mr90fj81-1", + "label": "Home", + "targetKind": "page", + "pageId": "page-mr90fj81-1" + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/NavbarItem", + "id": "nav-mr90fj81-2", + "label": "About", + "targetKind": "page", + "pageId": "page-mr90fj81-2" + } + ] + }, + "pages": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Page", + "id": "page-mr90fj81-1", + "title": "Home", + "layout": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridSize", + "columns": 24, + "rows": 16, + "rowGap": "8px", + "columnGap": "8px" + }, + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 1, + "rowEnd": 2 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Heading", + "recipe": { + "name": "heading", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/static-heading", + "config": { + "text": "Welcome to Acme", + "level": 1, + "align": "left" + } + } + } + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 2, + "rowEnd": 5 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Text", + "recipe": { + "name": "text", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/markdown", + "config": { + "content": "Acme builds delightful things. This whole site was assembled with the **drag-and-drop website builder** \u2014 no code required.\n\nUse the navbar above to explore." + } + } + }, + "title": "Text" + } + ], + "itemBorder": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + }, + "children": [] + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Page", + "id": "page-mr90fj81-2", + "title": "About", + "layout": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridSize", + "columns": 24, + "rows": 16, + "rowGap": "8px", + "columnGap": "8px" + }, + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 1, + "rowEnd": 2 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Heading", + "recipe": { + "name": "heading", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/static-heading", + "config": { + "text": "About Acme", + "level": 1, + "align": "left" + } + } + } + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 2, + "rowEnd": 5 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Text", + "recipe": { + "name": "text", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/markdown", + "config": { + "content": "We are a small team with big ideas. Every page here is a grid of widgets you can rearrange visually." + } + } + }, + "title": "Text" + } + ], + "itemBorder": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + }, + "children": [] + } + ], + "name": "Acme_Marketing", + "title": "Acme Marketing", + "published": true +} diff --git a/example/app/demo_sites/teamHandbook.json b/example/app/demo_sites/teamHandbook.json new file mode 100644 index 000000000..d011b74ac --- /dev/null +++ b/example/app/demo_sites/teamHandbook.json @@ -0,0 +1,185 @@ +{ + "_id": "demo-site-team-handbook", + "type": "dmss://DemoDataSource/plugins/builder/sites/blueprints/Website", + "schemaVersion": 1, + "navbar": { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Navbar", + "enabled": true, + "brand": "Handbook", + "background": "#ffffff", + "color": "#3d3d3d", + "brandColor": "#007079", + "align": "right", + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/NavbarItem", + "id": "nav-mr90fj81-3", + "label": "Handbook", + "targetKind": "page", + "pageId": "page-mr90fj81-3" + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/NavbarItem", + "id": "nav-mr90fj81-4", + "label": "Onboarding", + "targetKind": "page", + "pageId": "page-mr90fj81-4" + } + ] + }, + "pages": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Page", + "id": "page-mr90fj81-3", + "title": "Handbook", + "layout": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridSize", + "columns": 24, + "rows": 16, + "rowGap": "8px", + "columnGap": "8px" + }, + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 1, + "rowEnd": 2 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Heading", + "recipe": { + "name": "heading", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/static-heading", + "config": { + "text": "Team Handbook", + "level": 1, + "align": "left" + } + } + } + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 2, + "rowEnd": 5 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Text", + "recipe": { + "name": "text", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/markdown", + "config": { + "content": "A living document for how we work. Edit any page in the builder and it updates here instantly." + } + } + }, + "title": "Text" + } + ], + "itemBorder": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + }, + "children": [] + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/builder/Page", + "id": "page-mr90fj81-4", + "title": "Onboarding", + "layout": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridPluginConfig", + "size": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridSize", + "columns": 24, + "rows": 16, + "rowGap": "8px", + "columnGap": "8px" + }, + "items": [ + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 1, + "rowEnd": 2 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Heading", + "recipe": { + "name": "heading", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/static-heading", + "config": { + "text": "Getting started", + "level": 1, + "align": "left" + } + } + } + }, + { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridItem", + "gridArea": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridArea", + "columnStart": 1, + "columnEnd": 12, + "rowStart": 2, + "rowEnd": 5 + }, + "viewConfig": { + "type": "dmss://system/SIMOS/InlineRecipeViewConfig", + "scope": "", + "label": "Text", + "recipe": { + "name": "text", + "type": "dmss://system/SIMOS/UiRecipe", + "plugin": "@development-framework/dm-core-plugins/markdown", + "config": { + "content": "1. Get your accounts.\n2. Read this handbook.\n3. Ship something small on day one." + } + } + }, + "title": "Text" + } + ], + "itemBorder": { + "type": "dmss://system/Plugins/dm-core-plugins/grid/GridBorder", + "size": "1px", + "style": "solid", + "color": "#bbb", + "radius": "5px" + }, + "showItemBorders": false + }, + "children": [] + } + ], + "name": "Team_Handbook", + "title": "Team Handbook", + "published": true +} diff --git a/example/reset-app.sh b/example/reset-app.sh index 6c1d5f502..43f35b905 100755 --- a/example/reset-app.sh +++ b/example/reset-app.sh @@ -105,6 +105,28 @@ main () { echo "Creating lookup table" dm --token "$TOKEN" --url $VITE_DMSS_URL create-lookup exampleApplication DemoDataSource/recipes + + # Seed a couple of ready-made websites into the "Sites" data source so the + # website directory isn't empty on first visit. These are stored via the + # raw add endpoint (like the builder's own "New site" action) because their + # inline widget configs are free-form objects the static importer rejects. + if [ -d app/demo_sites ]; then + echo "Seeding demo websites into 'Sites'" + AUTH_HEADER=() + if [ -n "$TOKEN" ]; then + AUTH_HEADER=(-H "Authorization: Bearer $TOKEN") + fi + for site_file in app/demo_sites/*.json; do + [ -e "$site_file" ] || continue + curl --silent --show-error --fail \ + -X POST "$VITE_DMSS_URL/api/documents-add-raw/Sites" \ + -H "Content-Type: application/json" \ + ${AUTH_HEADER[@]+"${AUTH_HEADER[@]}"} \ + --data-binary @"$site_file" > /dev/null && + echo " Seeded $(basename "$site_file") ✓" || + echo " WARNING: failed to seed $(basename "$site_file")" + done + fi } time main "$@" \ No newline at end of file diff --git a/example/src/ViewPage.tsx b/example/src/ViewPage.tsx index c79f6ce7b..7e4b0bcee 100644 --- a/example/src/ViewPage.tsx +++ b/example/src/ViewPage.tsx @@ -10,6 +10,9 @@ import { Typography } from '@equinor/eds-core-react' function ViewPage() { const urlParams = new URLSearchParams(window.location.search) const idReference = urlParams.get('documentId') + // Optional: pick a specific named UiRecipe (e.g. the builder's read-only + // "View" vs its "Edit" recipe) so one entity can be opened either way. + const recipeName = urlParams.get('recipe') ?? undefined if (!idReference) throw new Error("No parameter 'documentId' in URL") @@ -36,7 +39,13 @@ function ViewPage() { ) } - return + return ( + + ) } export default ViewPage diff --git a/package-lock.json b/package-lock.json index 763c4a032..3e17ac237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4631,6 +4631,15 @@ "node": ">=0.4.0" } }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5334,6 +5343,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -5611,6 +5633,15 @@ "node": ">= 0.12.0" } }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", @@ -5943,6 +5974,18 @@ "integrity": "sha512-iSPlClZP8vX7MC3/u6s3lrDuoQyhQukh5LyABJ3hvfzbQ3Yyayd4fp04zjLnfi267B/B2FkumcWWgrbban7sSA==", "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -7567,6 +7610,15 @@ "node": ">= 6" } }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -13448,6 +13500,18 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", @@ -15416,6 +15480,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -15569,6 +15651,27 @@ } } }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -15799,6 +15902,7 @@ "react-toastify": "^11.1.0", "remark-gfm": "^4.0.1", "ts-node": "^10.9.2", + "xlsx": "^0.18.5", "yaml": "^2.8.3" }, "devDependencies": { diff --git a/packages/dm-core-plugins/blueprints/builder/BuilderPluginConfig.json b/packages/dm-core-plugins/blueprints/builder/BuilderPluginConfig.json new file mode 100644 index 000000000..5bd01bc15 --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/BuilderPluginConfig.json @@ -0,0 +1,50 @@ +{ + "name": "BuilderPluginConfig", + "type": "dmss://system/SIMOS/Blueprint", + "description": "Configuration for the drag-and-drop website builder plugin.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "mode", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Initial mode: 'edit' (canvas) or 'preview' (runtime render).", + "default": "edit", + "optional": true + }, + { + "name": "initialConfig", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/grid/GridPluginConfig", + "description": "Seed grid config used to hydrate the canvas.", + "optional": true + }, + { + "name": "readOnly", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "description": "When true, render the built site as a read-only viewer (navbar, navigation and content only) with no editing toolbar, palette or inspector.", + "default": false, + "optional": true + }, + { + "name": "editorRoles", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "dimensions": "*", + "description": "Role names allowed to edit. When set, users whose current role is not listed get the read-only viewer. Empty or absent means editing is open to everyone.", + "optional": true + }, + { + "name": "directoryUrl", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "URL to navigate to when clicking the 'Website builder' breadcrumb (e.g. the site directory page). Falls back to window.history.back() when omitted.", + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/Navbar.json b/packages/dm-core-plugins/blueprints/builder/Navbar.json new file mode 100644 index 000000000..890639a83 --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/Navbar.json @@ -0,0 +1,68 @@ +{ + "name": "Navbar", + "type": "dmss://system/SIMOS/Blueprint", + "description": "The site's customizable top navigation bar, shared across every page.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "enabled", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "description": "Whether the navbar is shown. Disabled by default so existing sites are unchanged.", + "default": false, + "optional": true + }, + { + "name": "brand", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Brand / site name shown at the start of the bar.", + "default": "My Site", + "optional": true + }, + { + "name": "background", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Bar background colour.", + "default": "#ffffff", + "optional": true + }, + { + "name": "color", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Link text colour.", + "default": "#3d3d3d", + "optional": true + }, + { + "name": "brandColor", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Brand text colour.", + "default": "#007079", + "optional": true + }, + { + "name": "align", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Horizontal placement of the link group: 'left', 'center' or 'right'.", + "default": "right", + "optional": true + }, + { + "name": "items", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/builder/NavbarItem", + "description": "The clickable links shown in the bar.", + "dimensions": "*", + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/NavbarItem.json b/packages/dm-core-plugins/blueprints/builder/NavbarItem.json new file mode 100644 index 000000000..879dfa29d --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/NavbarItem.json @@ -0,0 +1,44 @@ +{ + "name": "NavbarItem", + "type": "dmss://system/SIMOS/Blueprint", + "description": "A single clickable link in the top navbar. Links either navigate to a page in the site or open an external URL.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "id", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Stable handle for this navbar item." + }, + { + "name": "label", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Text shown for the link." + }, + { + "name": "targetKind", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Where the link points: 'page' (navigate within the site) or 'url' (external address)." + }, + { + "name": "pageId", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "For 'page' links: the id of the target page.", + "optional": true + }, + { + "name": "href", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "For 'url' links: the external address to open.", + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/Page.json b/packages/dm-core-plugins/blueprints/builder/Page.json new file mode 100644 index 000000000..26d3c818e --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/Page.json @@ -0,0 +1,38 @@ +{ + "name": "Page", + "type": "dmss://system/SIMOS/Blueprint", + "description": "A single navigable page in a builder site. Its layout is a grid config, and it may contain nested sub-pages to any depth.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "id", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Stable handle used by the nav sidebar to switch/route to this page." + }, + { + "name": "title", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Page title; doubles as the navigation label." + }, + { + "name": "layout", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/grid/GridPluginConfig", + "description": "The page's grid model, in the same format the runtime grid plugin consumes." + }, + { + "name": "children", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/builder/Page", + "description": "Sub-pages reachable from this page's nav menu, nestable to any depth.", + "dimensions": "*", + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/Site.json b/packages/dm-core-plugins/blueprints/builder/Site.json new file mode 100644 index 000000000..eaaacc532 --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/Site.json @@ -0,0 +1,49 @@ +{ + "name": "Site", + "type": "dmss://system/SIMOS/Blueprint", + "description": "A website built with the drag-and-drop builder: a shared top navbar plus an ordered tree of pages.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "title", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Human-friendly display name shown on the site directory card. Unlike the DMSS document name (which must be slug-safe), this may contain spaces and punctuation.", + "optional": true + }, + { + "name": "published", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "description": "Whether the site is published (live). The site directory only surfaces published sites to visitors; authors still see their unpublished drafts.", + "default": false, + "optional": true + }, + { + "name": "schemaVersion", + "type": "CORE:BlueprintAttribute", + "attributeType": "number", + "description": "Version of the serialized site format, used to migrate older saved sites.", + "default": 1, + "optional": true + }, + { + "name": "navbar", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/builder/Navbar", + "description": "The customizable top navigation bar shared by every page.", + "optional": true + }, + { + "name": "pages", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/builder/Page", + "description": "The site's top-level pages, in nav order. There is always at least one.", + "dimensions": "*" + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/SiteDirectoryConfig.json b/packages/dm-core-plugins/blueprints/builder/SiteDirectoryConfig.json new file mode 100644 index 000000000..7e3d8b147 --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/SiteDirectoryConfig.json @@ -0,0 +1,70 @@ +{ + "name": "SiteDirectoryConfig", + "type": "dmss://system/SIMOS/Blueprint", + "description": "Configuration for the Site directory plugin: a gallery of saved websites with an optional 'New site' action.", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "heading", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Heading shown above the gallery. Defaults to 'Websites'.", + "optional": true + }, + { + "name": "entityType", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Entity type to list. Defaults to the builder Site type (PLUGINS:dm-core-plugins/builder/Site).", + "optional": true + }, + { + "name": "dataSources", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "dimensions": "*", + "description": "Data sources to search. Defaults to the application's visible data sources.", + "optional": true + }, + { + "name": "targetDataSource", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "DMSS address (usually a data source root) where 'New site' creates the entity. Defaults to the first searched data source; when absent the create button is hidden.", + "optional": true + }, + { + "name": "viewUrlTemplate", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "URL opened when visiting a site. {id} and {dataSource} are substituted. Defaults to '/view?documentId=dmss://{dataSource}/{id}'.", + "optional": true + }, + { + "name": "newSiteUrlTemplate", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "URL opened right after 'New site' creates a site (empty sites usually open in the editor, e.g. append '&recipe=Edit'). {id} and {dataSource} are substituted. Falls back to viewUrlTemplate.", + "optional": true + }, + { + "name": "newSiteName", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "description": "Name given to a site created via 'New site'. Defaults to 'New_site'.", + "optional": true + }, + { + "name": "allowCreate", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "description": "Set false to hide the 'New site' button (a read-only gallery).", + "default": true, + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/blueprints/builder/package.json b/packages/dm-core-plugins/blueprints/builder/package.json new file mode 100644 index 000000000..adc64dd4d --- /dev/null +++ b/packages/dm-core-plugins/blueprints/builder/package.json @@ -0,0 +1,25 @@ +{ + "name": "builder", + "type": "CORE:Package", + "isRoot": false, + "_meta_": { + "type": "CORE:Meta", + "version": "0.0.1", + "dependencies": [ + { + "type": "CORE:Dependency", + "alias": "CORE", + "address": "system/SIMOS", + "version": "0.0.1", + "protocol": "dmss" + }, + { + "type": "CORE:Dependency", + "alias": "PLUGINS", + "address": "system/Plugins", + "version": "0.0.1", + "protocol": "dmss" + } + ] + } +} diff --git a/packages/dm-core-plugins/blueprints/grid/GridItem.json b/packages/dm-core-plugins/blueprints/grid/GridItem.json index 87e349346..20cb9c51c 100644 --- a/packages/dm-core-plugins/blueprints/grid/GridItem.json +++ b/packages/dm-core-plugins/blueprints/grid/GridItem.json @@ -22,6 +22,18 @@ "name": "viewConfig", "type": "CORE:BlueprintAttribute", "attributeType": "CORE:ViewConfig" + }, + { + "name": "style", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/grid/GridItemStyle", + "optional": true + }, + { + "name": "titleStyle", + "type": "CORE:BlueprintAttribute", + "attributeType": "PLUGINS:dm-core-plugins/grid/GridItemStyle", + "optional": true } ] } diff --git a/packages/dm-core-plugins/blueprints/grid/GridItemStyle.json b/packages/dm-core-plugins/blueprints/grid/GridItemStyle.json new file mode 100644 index 000000000..dd841905c --- /dev/null +++ b/packages/dm-core-plugins/blueprints/grid/GridItemStyle.json @@ -0,0 +1,47 @@ +{ + "name": "GridItemStyle", + "type": "dmss://system/SIMOS/Blueprint", + "attributes": [ + { + "name": "type", + "type": "CORE:BlueprintAttribute", + "attributeType": "string" + }, + { + "name": "textAlign", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + }, + { + "name": "fontSize", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + }, + { + "name": "verticalAlign", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + }, + { + "name": "bold", + "type": "CORE:BlueprintAttribute", + "attributeType": "boolean", + "optional": true + }, + { + "name": "color", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + }, + { + "name": "padding", + "type": "CORE:BlueprintAttribute", + "attributeType": "string", + "optional": true + } + ] +} diff --git a/packages/dm-core-plugins/jestSetup.js b/packages/dm-core-plugins/jestSetup.js index 66a312cb4..f543e025b 100644 --- a/packages/dm-core-plugins/jestSetup.js +++ b/packages/dm-core-plugins/jestSetup.js @@ -1,5 +1,25 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires +// JSDOM doesn't implement HTMLDialogElement.showModal/close. +// Polyfill them so EDS (which uses the native element) works in tests. +if (typeof HTMLDialogElement !== 'undefined') { + if (!HTMLDialogElement.prototype.show) { + HTMLDialogElement.prototype.show = function () { + this.open = true + } + } + if (!HTMLDialogElement.prototype.showModal) { + HTMLDialogElement.prototype.showModal = function () { + this.open = true + } + } + if (!HTMLDialogElement.prototype.close) { + HTMLDialogElement.prototype.close = function () { + this.open = false + } + } +} + beforeEach(() => { window.sessionStorage.clear() }) diff --git a/packages/dm-core-plugins/package.json b/packages/dm-core-plugins/package.json index bf955ec35..22119def8 100644 --- a/packages/dm-core-plugins/package.json +++ b/packages/dm-core-plugins/package.json @@ -22,6 +22,7 @@ "react-toastify": "^11.1.0", "remark-gfm": "^4.0.1", "ts-node": "^10.9.2", + "xlsx": "^0.18.5", "yaml": "^2.8.3" }, "devDependencies": { @@ -57,6 +58,22 @@ "dist" ], "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./builder": { + "types": "./dist/builder/index.d.ts", + "default": "./dist/builder/index.js" + }, + "./grid": { + "types": "./dist/grid/index.d.ts", + "default": "./dist/grid/index.js" + }, + "./package.json": "./package.json", + "./*": "./*" + }, "scripts": { "prebuild": "shx rm -rf dist", "build": "tsc", diff --git a/packages/dm-core-plugins/src/builder/BuilderPlugin.tsx b/packages/dm-core-plugins/src/builder/BuilderPlugin.tsx new file mode 100644 index 000000000..fb047b152 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/BuilderPlugin.tsx @@ -0,0 +1,1019 @@ +import { + ErrorBoundary, + type IUIPlugin, + splitAddress, + useApplication, + useDocument, +} from '@development-framework/dm-core' +import { + DndContext, + type DragEndEvent, + KeyboardSensor, + PointerSensor, + pointerWithin, + useSensor, + useSensors, +} from '@dnd-kit/core' +import { + Button, + Icon, + Input, + Tooltip, + Typography, +} from '@equinor/eds-core-react' +import { + Fragment, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react' +import type { TGridArea, TGridItem } from '../grid' +import { Canvas, DENSITY_STEP } from './components/Canvas' +import { Inspector } from './components/Inspector' +import { Navbar } from './components/Navbar' +import { NavSidebar } from './components/NavSidebar' +import { Outline } from './components/Outline' +import { SiteGrid } from './components/SiteGrid' +import { TemplatesMenu } from './components/TemplatesMenu' +import { Toast } from './components/Toast' +import { WidgetPalette } from './components/WidgetPalette' +import { getBlock } from './model/blocks' +import { useHistory } from './model/history' +import { + addWidget, + clampPath, + deserialize, + duplicateWidget, + getSubModel, + insertWidgetItem, + MAX_GRID_CELLS, + MIN_GRID_CELLS, + moveWidget, + removeWidget, + rescaleGrid, + resizeWidget, + serialize, + setSubModel, + setWidgetArea, + setWidgetConfigValue, + setWidgetLabel, + setWidgetScope, + setWidgetStyleValue, + setWidgetTitle, + translateArea, + wouldOverlap, +} from './model/model' +import { + addNavItem, + addPage, + deserializeSite, + findPage, + findPageContext, + isSerializedSite, + moveNavItem, + movePage, + removeNavItem, + removePage, + renamePage, + resolvePluginAliases, + serializeSite, + setPageLayout, + type TNavbar, + type TNavbarItem, + updateNavbar, + updateNavItem, + updateSiteMeta, +} from './model/site' +import * as Styled from './styles' +import type { + TBlock, + TBuilderMode, + TBuilderModel, + TBuilderPluginConfig, +} from './types' +import { ICONS } from './utils/icons' +import { useToast } from './utils/toast' + +type TDevice = 'desktop' | 'tablet' | 'mobile' + +const DEVICE_WIDTHS: Record = { + desktop: '100%', + tablet: '768px', + mobile: '380px', +} + +const DEVICE_ICONS: Record = { + desktop: 'desktop_mac', + tablet: 'tablet_android', + mobile: 'phone', +} + +export const BuilderPlugin = ( + props: IUIPlugin & { config?: TBuilderPluginConfig } +): React.ReactElement => { + const { config, idReference, type, onSubmit, onChange } = props + + const { document } = useDocument>(idReference, 1) + + const history = useHistory(() => + deserializeSite(config?.initialConfig ?? null) + ) + const site = history.present + const setSite = history.set + const [activePageId, setActivePageId] = useState( + () => site.pages[0]?.id ?? '' + ) + const readOnly = config?.readOnly ?? false + const { role, dmssAPI } = useApplication() + // When `editorRoles` is configured, only those roles may edit; everyone else + // is shown the read-only viewer. An empty/absent list means editing is open. + const editorRoles = config?.editorRoles + const roleAllowsEditing = + !editorRoles?.length || editorRoles.includes(role?.name) + const locked = readOnly || !roleAllowsEditing + const [mode, setMode] = useState( + locked ? 'preview' : (config?.mode ?? 'edit') + ) + const [selectedIndex, setSelectedIndex] = useState(null) + const [showJson, setShowJson] = useState(false) + const [path, setPath] = useState([]) + const [device, setDevice] = useState('desktop') + const [clipboard, setClipboard] = useState(null) + const { toast, notify, dismiss } = useToast() + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor) + ) + + // The page currently open in the editor; falls back to the first page when + // the active id no longer resolves (e.g. after a delete or undo). + const activePage = findPage(site, activePageId) ?? site.pages[0] + const model = activePage.layout + + // Edit the active page's grid through the site history, so widget edits and + // page operations share a single undo stack. Behaves like a plain model + // setter scoped to the open page. + const setModel = useCallback( + ( + updater: TBuilderModel | ((current: TBuilderModel) => TBuilderModel), + label: string | null = null + ) => + setSite((current) => { + const previous = findPage(current, activePage.id)?.layout + if (previous === undefined) return current + const next = + typeof updater === 'function' + ? (updater as (value: TBuilderModel) => TBuilderModel)(previous) + : updater + return setPageLayout(current, activePage.id, next) + }, label), + [setSite, activePage.id] + ) + + // The grid currently being edited (root, or a section drilled into via path). + const activeModel = getSubModel(model, path) + const frameWidth = DEVICE_WIDTHS[device] + + /** + * Apply a transform to the active (possibly nested) grid and write it back. + * An optional `label` lets consecutive related changes (e.g. a drag-resize or + * typing in one field) collapse into a single undo step. + */ + const updateActive = ( + updater: (model: TBuilderModel) => TBuilderModel, + label: string | null = null + ) => + setModel( + (current) => + setSubModel(current, path, updater(getSubModel(current, path))), + label + ) + + const handleAdd = (block: TBlock) => updateActive((m) => addWidget(m, block)) + + const handleDragEnd = (event: DragEndEvent) => { + const { active, over } = event + if (!over || over.id !== 'canvas') return + const blockId = active.data.current?.blockId + if (!blockId) return + const block = getBlock(blockId) + if (block) handleAdd(block) + } + + const handleDelete = (index: number) => { + updateActive((m) => removeWidget(m, index)) + setSelectedIndex(null) + notify('Widget deleted') + } + + const handleDuplicate = (index: number) => { + updateActive((m) => duplicateWidget(m, index)) + notify('Widget duplicated') + } + + const handleEnter = (index: number) => { + setPath((current) => [...current, index]) + setSelectedIndex(null) + } + + const handleNavigate = (depth: number) => { + setPath((current) => current.slice(0, depth)) + setSelectedIndex(null) + } + + const handleApplyTemplate = (build: () => TBuilderModel) => { + setModel(build()) + setPath([]) + setSelectedIndex(null) + notify('Template applied') + } + + // ---- Page (nav sidebar) management ------------------------------------- + + // Switch the editor (or, in preview, the viewer) to another page, resetting + // the section path and selection so we open at that page's root grid. + const handleSelectPage = (id: string) => { + if (id === activePageId) return + setActivePageId(id) + setPath([]) + setSelectedIndex(null) + } + + const handleAddPage = (parentId: string | null = null) => { + const { site: next, id } = addPage(site, parentId) + if (!id) return + setSite(next) + setActivePageId(id) + setPath([]) + setSelectedIndex(null) + notify(parentId ? 'Sub-page added' : 'Page added') + } + + const handleRenamePage = (id: string, title: string) => + setSite((current) => renamePage(current, id, title)) + + const handleDeletePage = (id: string) => { + const context = findPageContext(site, id) + if (!context) return + if (context.parentId === null && site.pages.length <= 1) return + const next = removePage(site, id) + if (next === site) return + setSite(next) + if (id === activePageId || findPage(next, activePageId) === null) { + const sibling = + context.siblings[context.index - 1] ?? context.siblings[context.index] + const fallback = + (sibling && sibling.id !== id ? findPage(next, sibling.id) : null) ?? + (context.parentId ? findPage(next, context.parentId) : null) ?? + next.pages[0] + setActivePageId(fallback.id) + setPath([]) + setSelectedIndex(null) + } + notify('Page deleted') + } + + const handleReorderPages = ( + parentId: string | null, + from: number, + to: number + ) => setSite((current) => movePage(current, parentId, from, to)) + + // ---- Top navbar management --------------------------------------------- + + const handleUpdateNavbar = (patch: Partial) => + setSite((current) => updateNavbar(current, patch)) + + const handleAddNavItem = () => setSite((current) => addNavItem(current).site) + + const handleUpdateNavItem = (id: string, patch: Partial) => + setSite((current) => updateNavItem(current, id, patch)) + + const handleRemoveNavItem = (id: string) => + setSite((current) => removeNavItem(current, id)) + + const handleReorderNavItem = (from: number, to: number) => + setSite((current) => moveNavItem(current, from, to)) + + // ---- Site metadata (display name + publish state) ---------------------- + + const handleRenameSite = (title: string) => + setSite((current) => updateSiteMeta(current, { title }), 'rename-site') + + const handleTogglePublished = () => + setSite( + (current) => updateSiteMeta(current, { published: !current.published }), + 'publish' + ) + + const handleMove = ( + index: number, + deltaColumns: number, + deltaRows: number + ) => { + const item = activeModel.items[index] + if (!item) return + const area = translateArea(item.gridArea, deltaColumns, deltaRows) + // Pre-check against the rendered grid for immediate feedback. + const preview = moveWidget(activeModel, index, area) + if (wouldOverlap(preview, index, preview.items[index].gridArea)) { + notify('That position overlaps another widget') + return + } + // Authoritative write re-derives from the latest model so a concurrent edit + // is never dropped, and re-checks overlap against that model. + updateActive((m) => { + const current = m.items[index] + if (!current) return m + const next = moveWidget( + m, + index, + translateArea(current.gridArea, deltaColumns, deltaRows) + ) + if (wouldOverlap(next, index, next.items[index].gridArea)) return m + return next + }) + } + + const handleResize = (index: number, area: TGridArea) => + updateActive((m) => { + if (!m.items[index]) return m + const next = resizeWidget(m, index, area) + if (wouldOverlap(next, index, next.items[index].gridArea)) return m + return next + }, `resize:${index}`) + + // Drag-resize emits many updates; end the coalescing run on pointer-up so the + // whole gesture collapses into a single undo step. + const handleResizeEnd = () => history.commit() + + // Rescale the active grid's density (cell count) without changing its size, + // so a higher density yields a finer snap stride for micro adjustments. + // Consecutive density steps coalesce into one undo step via a shared label. + const handleDensity = (factor: number) => + updateActive((m) => rescaleGrid(m, factor), 'density') + + const handleSetArea = (index: number, area: TGridArea) => { + const item = activeModel.items[index] + if (!item) return + const preview = setWidgetArea(activeModel, index, area) + if (wouldOverlap(preview, index, preview.items[index].gridArea)) { + notify('That size overlaps another widget') + return + } + updateActive((m) => { + if (!m.items[index]) return m + const next = setWidgetArea(m, index, area) + if (wouldOverlap(next, index, next.items[index].gridArea)) return m + return next + }, `area:${index}`) + } + + const handleCopy = (index: number) => { + const item = activeModel.items[index] + if (!item) return + setClipboard(JSON.parse(JSON.stringify(item)) as TGridItem) + notify('Widget copied') + } + + const handlePaste = () => { + if (!clipboard) return + // The pasted widget is appended, so it lands at the current item count. + const newIndex = activeModel.items.length + updateActive((m) => insertWidgetItem(m, clipboard)) + setSelectedIndex(newIndex) + notify('Widget pasted') + } + + const selectedItem = + selectedIndex !== null ? (activeModel.items[selectedIndex] ?? null) : null + const selectedBlock = selectedItem + ? getBlock( + typeof selectedItem.viewConfig.recipe === 'string' + ? selectedItem.viewConfig.recipe + : (selectedItem.viewConfig.recipe?.name ?? '') + ) + : undefined + + const inspectorHandlers = { + onTitle: (value: string) => + selectedIndex !== null && + updateActive( + (m) => setWidgetTitle(m, selectedIndex, value), + `title:${selectedIndex}` + ), + onLabel: (value: string) => + selectedIndex !== null && + updateActive( + (m) => setWidgetLabel(m, selectedIndex, value), + `label:${selectedIndex}` + ), + onScope: (value: string) => + selectedIndex !== null && + updateActive( + (m) => setWidgetScope(m, selectedIndex, value), + `scope:${selectedIndex}` + ), + onArea: (area: TGridArea) => + selectedIndex !== null && handleSetArea(selectedIndex, area), + onConfigValue: (key: string, value: unknown) => + selectedIndex !== null && + updateActive( + (m) => setWidgetConfigValue(m, selectedIndex, key, value), + `config:${key}:${selectedIndex}` + ), + onStyleValue: ( + key: keyof NonNullable, + value: unknown, + target: 'body' | 'title' = 'body' + ) => + selectedIndex !== null && + updateActive( + (m) => setWidgetStyleValue(m, selectedIndex, key, value, target), + `style:${target}:${key}:${selectedIndex}` + ), + onCommit: () => history.commit(), + } + + // Keyboard shortcuts for editing. Undo/redo are available whenever the editor + // is active; the editing shortcuts are ignored while typing in a form field so + // the field's own behaviour (text undo, copy, etc.) keeps working. + useEffect(() => { + if (mode !== 'edit' || showJson) return + + const onKeyDown = (event: KeyboardEvent) => { + const target = event.target as HTMLElement | null + const typing = + !!target && + (target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable) + const mod = event.metaKey || event.ctrlKey + const key = event.key.toLowerCase() + + if (mod && key === 'z') { + if (typing) return + event.preventDefault() + if (event.shiftKey) history.redo() + else history.undo() + return + } + if (mod && key === 'y') { + if (typing) return + event.preventDefault() + history.redo() + return + } + + if (typing) return + + if (mod && key === 'c') { + if (selectedIndex !== null) handleCopy(selectedIndex) + return + } + if (mod && key === 'v') { + if (clipboard) { + event.preventDefault() + handlePaste() + } + return + } + if (mod && key === 'd') { + if (selectedIndex !== null) { + event.preventDefault() + handleDuplicate(selectedIndex) + } + return + } + if (key === 'delete' || key === 'backspace') { + if (selectedIndex !== null) { + event.preventDefault() + handleDelete(selectedIndex) + } + return + } + if (key === 'escape') setSelectedIndex(null) + } + + window.addEventListener('keydown', onKeyDown) + return () => window.removeEventListener('keydown', onKeyDown) + }, [ + mode, + showJson, + history, + selectedIndex, + clipboard, + handleCopy, + handlePaste, + handleDuplicate, + handleDelete, + ]) + + // Keep navigation valid: if the model changes underneath the current path + // (e.g. an undo removes the section being edited), truncate the path to the + // deepest still-valid container and drop any now-stale selection. + useEffect(() => { + const clamped = clampPath(model, path) + if (clamped.length !== path.length) { + setPath(clamped) + setSelectedIndex(null) + } + }, [model, path]) + + // Keep the active page id in sync with the site: if it no longer resolves + // (e.g. an undo/redo removed the open page), snap to the resolved page. + useEffect(() => { + if (activePage.id !== activePageId) setActivePageId(activePage.id) + }, [activePage.id, activePageId]) + + // Persisted JSON of the whole site (all pages). The baseline ref tracks what + // was last saved so we can tell whether there are unsaved changes. + const currentJson = useMemo(() => JSON.stringify(serializeSite(site)), [site]) + const savedJsonRef = useRef(currentJson) + const [saveState, setSaveState] = useState<'saved' | 'saving' | 'dirty'>( + 'saved' + ) + + // Hydrate the builder from the page entity's saved `layout` once it loads, so + // reopening shows previously saved work rather than the recipe seed. Accepts + // both the multi-page site format and a legacy single-grid layout. A corrupt + // or unreadable layout must never crash the editor: on failure we keep the + // seeded/empty site and tell the user, so their next save can recover it. + const hydratedRef = useRef(false) + useEffect(() => { + if (hydratedRef.current) return + // A Site-shaped document (top-level `pages`) *is* the site; otherwise the + // site is embedded in the host document's `layout` attribute (a page/host + // entity). Detecting by shape keeps existing `.layout` hosts unchanged. + const source = isSerializedSite(document) ? document : document?.layout + if (!source) return + hydratedRef.current = true + try { + const loaded = deserializeSite(source) + if (loaded.pages.length === 0) { + throw new Error('Saved layout has no pages') + } + savedJsonRef.current = JSON.stringify(serializeSite(loaded)) + setSite(() => loaded, 'load') + setActivePageId(loaded.pages[0]?.id ?? '') + setPath([]) + setSelectedIndex(null) + setSaveState('saved') + } catch { + // Leave the current (seeded) site in place and flag it as unsaved so the + // author can review before overwriting whatever unreadable data existed. + setSaveState('dirty') + notify('Could not read the saved page — starting from a blank layout') + } + }, [document, setSite, notify]) + + /** + * Persist the current site. Builder widgets serialize free-form inline + * `config` objects with no DMSS `type`, which the validating update endpoint + * rejects (it requires a `type` on every node). We therefore write through the + * context-free add-raw endpoint (the same path used to create a site), which + * stores the document verbatim and upserts by `_id`. When the bound document + * *is* the site (Site-shaped) we write the navbar/pages onto the document + * itself with fully-qualified addresses (aliases can't be resolved on read), + * preserving the document's own `_id`, `type` and metadata. Otherwise the site + * is stored in the host document's `layout` attribute. Without an entity we + * fall back to the host `onChange` handler. Returns true on success. + */ + const save = async (): Promise => { + const serializedSite = JSON.parse(currentJson) + setSaveState('saving') + try { + if (idReference && document) { + const { dataSource } = splitAddress(idReference) + const body = isSerializedSite(document) + ? { + ...document, + ...resolvePluginAliases(serializedSite), + type: document.type, + } + : { ...document, layout: resolvePluginAliases(serializedSite) } + await dmssAPI.documentAddSimple({ dataSourceId: dataSource, body }) + } else if (onChange) { + onChange(serializedSite) + } else { + setSaveState('dirty') + return false + } + savedJsonRef.current = currentJson + setSaveState('saved') + return true + } catch { + setSaveState('dirty') + notify('Could not save page') + return false + } + } + + // Autosave: debounce changes and persist them. Without a save target the page + // just stays marked as having unsaved changes. + useEffect(() => { + if (locked) return + if (currentJson === savedJsonRef.current) { + setSaveState('saved') + return + } + if (!idReference && !onChange) { + setSaveState('dirty') + return + } + setSaveState('saving') + const timer = setTimeout(() => { + void save() + }, 800) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentJson, onChange, idReference, document]) + + // Warn before leaving the page while there are unsaved changes. + useEffect(() => { + if (locked) return + if (saveState === 'saved') return + const handler = (event: BeforeUnloadEvent) => { + event.preventDefault() + event.returnValue = '' + } + window.addEventListener('beforeunload', handler) + return () => window.removeEventListener('beforeunload', handler) + }, [saveState, locked]) + + const saveStatusLabel = + saveState === 'saved' + ? 'All changes saved' + : saveState === 'saving' + ? 'Saving…' + : 'Unsaved changes' + + // Breadcrumb labels for the path from the root page down to the active grid. + const crumbLabels = ['Page'] + for (let depth = 0; depth < path.length; depth++) { + const parent = getSubModel(model, path.slice(0, depth)) + crumbLabels.push(parent.items[path[depth]]?.title ?? 'Section') + } + + const breadcrumb = + path.length > 0 ? ( + + {crumbLabels.map((label, depth) => ( + + {depth > 0 && } + handleNavigate(depth)} + > + {label} + + + ))} + + ) : undefined + + // Round-trip through serialize/deserialize so preview proves the stored JSON + // renders identically with the runtime grid plugin. + const previewConfig = deserialize(serialize(model)) + + const navSidebar = ( + + ) + + const navbar = ( + + ) + + // Read-only viewer: render the published site (navbar, page navigation and + // content) with no editing chrome. Reached when the recipe sets `readOnly` or + // the current role isn't allowed to edit. Multi-page navigation still works so + // end users can browse the built site, but nothing here can mutate or persist it. + if (locked) { + return ( + + + + {navbar} + + {navSidebar} + + + + + + + + + + ) + } + + return ( + + + + + { + if (config?.directoryUrl) { + window.location.assign(config.directoryUrl) + } else { + window.history.back() + } + }} + style={{ + background: 'none', + border: 'none', + padding: 0, + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + }} + > + + Website builder + + + {mode === 'edit' && ( + + ) => + handleRenameSite(event.target.value) + } + onBlur={() => history.commit()} + style={{ width: '12rem' }} + /> + + )} + {mode === 'edit' && !locked && ( + + + + {site.published ? 'Published' : 'Publish'} + + + )} + {mode === 'edit' && ( + handleApplyTemplate(template.build)} + /> + )} + {mode === 'edit' && ( + <> + + history.undo()} + > + + + + + history.redo()} + > + + + + > + )} + + + {mode === 'edit' && ( + + {saveStatusLabel} + + )} + {mode === 'edit' && ( + { + void save() + }} + > + + Save + + )} + {(['desktop', 'tablet', 'mobile'] as TDevice[]).map((value) => ( + setDevice(value)} + > + + + ))} + {mode === 'edit' && ( + <> + + handleDensity(1 / DENSITY_STEP)} + > + + + + + + {activeModel.size.columns}×{activeModel.size.rows} + + + + = MAX_GRID_CELLS && + activeModel.size.rows >= MAX_GRID_CELLS + } + onClick={() => handleDensity(DENSITY_STEP)} + > + + + + > + )} + setShowJson((value) => !value)} + > + Advanced: JSON + + setMode('edit')} + > + + Edit + + setMode('preview')} + > + + Preview + + + + + {mode === 'edit' && !showJson && ( + + + + + )} + + {showJson ? ( + /* Dark-theme code panel — these colours are intentional and have no EDS token equivalent. */ + + + {JSON.stringify(serializeSite(site), null, 2)} + + + ) : mode === 'edit' ? ( + + ) : ( + + + + {navbar} + + {navSidebar} + + + + + + + + + + )} + + {mode === 'edit' && !showJson && ( + + )} + + + + ) +} diff --git a/packages/dm-core-plugins/src/builder/SiteDirectoryPlugin.tsx b/packages/dm-core-plugins/src/builder/SiteDirectoryPlugin.tsx new file mode 100644 index 000000000..1b9cc82d8 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/SiteDirectoryPlugin.tsx @@ -0,0 +1,366 @@ +import { type IUIPlugin, useApplication } from '@development-framework/dm-core' +import { + Button, + Dialog, + Icon, + Switch, + Typography, +} from '@equinor/eds-core-react' +import { + add, + delete_to_trash, + refresh, + warning_outlined, +} from '@equinor/eds-icons' +import { useCallback, useEffect, useState } from 'react' +import { SiteCard, type TSiteHit } from './components/SiteCard' +import { + createEmptySite, + resolvePluginAliases, + SITE_TYPE_ADDRESS, + serializeSite, + stampWidgetConfigTypes, +} from './model/site' +import * as S from './styles/directory.styles' +import type { TSiteDirectoryConfig } from './types/siteDirectory' + +// The literal `$` before the `{id}` placeholder marks a DMSS root-document id; +// it is not a template-string interpolation. +// biome-ignore lint/suspicious/noTemplateCurlyInString: literal `${id}` placeholder +const DEFAULT_VIEW_URL = '/view?documentId=dmss://{dataSource}/${id}' + +/** DMSS names allow only alphanumerics, underscore and dash. */ +const toSlug = (value: string): string => + value + .trim() + .replace(/[^a-zA-Z0-9_-]+/g, '_') + .replace(/^_+|_+$/g, '') || 'New_site' + +/** Strip a leading `dmss://` and any path, leaving the data source id. */ +const dataSourceOf = (address: string): string => + address.replace(/^dmss:\/\//, '').split('/')[0] + +/** Exported so tests can spy on it without fighting jsdom's non-configurable window.location. */ +export const navigateTo = (url: string) => window.location.assign(url) + +// ---- Directory plugin --------------------------------------------------- + +/** + * Site directory — a gallery of saved websites. + * + * Searches DMSS for every entity of the builder's Site type and lists them as + * cards that open the (read-only) site viewer, so people can actually browse + * what they build. Optionally creates a fresh site and jumps into the editor. + * + * It is intentionally routing-agnostic: cards are plain links built from + * `config.viewUrlTemplate`, defaulting to the example app's `/view?documentId=` + * convention, so it works in any host app without a hard router dependency. + */ +export const SiteDirectoryPlugin = ( + props: IUIPlugin & { config?: TSiteDirectoryConfig } +): React.ReactElement => { + const { config } = props + const { dmssAPI, visibleDataSources } = useApplication() + + const entityType = config?.entityType ?? SITE_TYPE_ADDRESS + const dataSources = + config?.dataSources && config.dataSources.length > 0 + ? config.dataSources + : visibleDataSources + const heading = config?.heading ?? 'Websites' + const allowCreate = config?.allowCreate ?? true + const target = config?.targetDataSource ?? dataSources[0] + + const [sites, setSites] = useState([]) + const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading') + const [reloadKey, setReloadKey] = useState(0) + const [creating, setCreating] = useState(false) + const [createError, setCreateError] = useState(null) + const [deletingId, setDeletingId] = useState(null) + const [deleteError, setDeleteError] = useState(null) + const [pendingDeleteSite, setPendingDeleteSite] = useState( + null + ) + // Authors need to see their drafts to finish and publish them, so drafts are + // shown by default; toggling this off previews the published-only view. + const [showDrafts, setShowDrafts] = useState(true) + + const buildUrl = useCallback( + (template: string | undefined, dataSource: string, id: string): string => + (template ?? DEFAULT_VIEW_URL) + .replace('{dataSource}', dataSource) + .replace('{id}', id), + [] + ) + + const viewUrl = useCallback( + (dataSource: string, id: string) => + buildUrl(config?.viewUrlTemplate, dataSource, id), + [buildUrl, config?.viewUrlTemplate] + ) + + const editUrl = useCallback( + (dataSource: string, id: string): string | null => + config?.newSiteUrlTemplate + ? buildUrl(config.newSiteUrlTemplate, dataSource, id) + : null, + [buildUrl, config?.newSiteUrlTemplate] + ) + + // Freshly created sites are empty, so they open in the editor (falling back to + // the normal view URL when no dedicated create URL is configured). + const createUrl = useCallback( + (dataSource: string, id: string): string => + buildUrl( + config?.newSiteUrlTemplate ?? config?.viewUrlTemplate, + dataSource, + id + ), + [buildUrl, config?.newSiteUrlTemplate, config?.viewUrlTemplate] + ) + + useEffect(() => { + let cancelled = false + setStatus('loading') + dmssAPI + .search({ dataSources, body: { type: entityType } }) + .then((response) => { + if (cancelled) return + const data = (response.data ?? {}) as Record< + string, + { _id?: string; name?: string; title?: string; published?: boolean } + > + const hits: TSiteHit[] = Object.entries(data).map(([address, doc]) => { + const id = doc._id ?? address.split('/').slice(1).join('/') + const name = doc.name ?? id ?? 'Untitled site' + return { + address, + dataSource: dataSourceOf(address), + id, + name, + label: + typeof doc.title === 'string' && doc.title.trim() !== '' + ? doc.title + : name, + published: doc.published === true, + } + }) + hits.sort((a, b) => a.label.localeCompare(b.label)) + setSites(hits) + setStatus('ready') + }) + .catch((error) => { + if (cancelled) return + console.error('Site directory search failed', error) + setStatus('error') + }) + return () => { + cancelled = true + } + // dataSources is derived from config/context; join to a stable dep. + }, [dmssAPI, entityType, dataSources.join(','), reloadKey]) + + const handleCreate = useCallback(async () => { + if (!target) return + setCreating(true) + setCreateError(null) + try { + const dataSource = dataSourceOf(target) + // DMSS names allow only alphanumerics/underscore/dash; add a short suffix + // so repeated "New site" clicks don't collide. + const name = `${toSlug(config?.newSiteName ?? 'New_site')}_${Date.now().toString(36)}` + // The context-free add-raw endpoint stores the document verbatim, so emit + // fully-qualified addresses (not the `PLUGINS:` alias) and stamp the root + // `type` with the listed entity type so the new site is found and rendered. + const body = { + ...resolvePluginAliases(serializeSite(createEmptySite())), + type: entityType, + name, + } + const response = await dmssAPI.documentAddSimple({ + dataSourceId: dataSource, + body, + }) + const id = typeof response.data === 'string' ? response.data : name + navigateTo(createUrl(dataSource, id)) + } catch (error) { + console.error('Failed to create site', error) + setCreateError('Could not create a new site. Please try again.') + setCreating(false) + } + }, [config?.newSiteName, dmssAPI, entityType, target, createUrl]) + + const handleDelete = useCallback( + async (site: TSiteHit) => { + setDeletingId(site.id) + setDeleteError(null) + const address = `dmss://${site.dataSource}/$${site.id}` + try { + // documentRemove takes a full DMSS address; root documents use the + // `$id` syntax (the `$` is DMSS's root-id sigil, not a JS template). + await dmssAPI.documentRemove({ address }) + setSites((current) => current.filter((s) => s.id !== site.id)) + setPendingDeleteSite(null) + } catch (error) { + // Sites saved before widget configs were stamped with a resolvable + // `type` (see WIDGET_CONFIG_TYPE) can't be deleted: DMSS fails to build + // the node-tree over their typeless configs. Self-heal such legacy + // documents by re-storing them with stamped config types, then retry. + try { + const current = (await dmssAPI + .documentGet({ address, depth: 0 }) + .then((response) => response.data)) as Record + stampWidgetConfigTypes(current) + await dmssAPI.documentAddSimple({ + dataSourceId: site.dataSource, + body: current, + }) + await dmssAPI.documentRemove({ address }) + setSites((sites) => sites.filter((s) => s.id !== site.id)) + setPendingDeleteSite(null) + } catch (retryError) { + console.error('Failed to delete site', error, retryError) + setDeleteError('Could not delete the site. Please try again.') + } + } finally { + setDeletingId(null) + } + }, + [dmssAPI] + ) + + const draftCount = sites.filter((site) => !site.published).length + const visibleSites = showDrafts + ? sites + : sites.filter((site) => site.published) + + return ( + + {/* EDS Dialog for delete confirmation */} + { + if (deletingId === null) setPendingDeleteSite(null) + }} + > + + Delete website? + + + + Deleting {pendingDeleteSite?.label} is permanent. + Are you sure you wish to delete it? + + {deleteError ? ( + + {deleteError} + + ) : null} + + + setPendingDeleteSite(null)} + disabled={deletingId !== null} + > + Cancel + + + pendingDeleteSite ? handleDelete(pendingDeleteSite) : undefined + } + disabled={deletingId !== null} + > + + {deletingId !== null ? 'Deleting…' : 'Delete'} + + + + + {/* Header row */} + + {heading} + + {draftCount > 0 ? ( + ) => + setShowDrafts(event.target.checked) + } + /> + ) : null} + setReloadKey((key) => key + 1)} + > + + + {allowCreate && target ? ( + + + {creating ? 'Creating…' : 'New site'} + + ) : null} + + + + {createError ? ( + {createError} + ) : null} + + {status === 'loading' ? ( + Loading websites… + ) : null} + + {status === 'error' ? ( + + + + Could not load websites. Check your data source access and try + refreshing. + + + ) : null} + + {status === 'ready' && sites.length === 0 ? ( + + No websites yet + {allowCreate && target ? ( + + Click New site to create your first one. + + ) : null} + + ) : null} + + {status === 'ready' && sites.length > 0 && visibleSites.length === 0 ? ( + + No published websites yet. Turn on "Show drafts" to see unpublished + sites. + + ) : null} + + {status === 'ready' && visibleSites.length > 0 ? ( + + {visibleSites.map((site) => ( + { + setDeleteError(null) + setPendingDeleteSite(site) + }} + deleting={deletingId === site.id} + /> + ))} + + ) : null} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/BuilderToolbar.styles.ts b/packages/dm-core-plugins/src/builder/components/BuilderToolbar.styles.ts new file mode 100644 index 000000000..c327b1e0c --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/BuilderToolbar.styles.ts @@ -0,0 +1,29 @@ +import { Input, Typography } from '@equinor/eds-core-react' +import styled from 'styled-components' + +/** The "Website builder" title doubles as a back link to the site directory. */ +export const BackButton = styled.button` + background: none; + border: none; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; +` + +export const BackTitle = styled(Typography)` + text-decoration: underline; + text-decoration-style: dotted; + text-underline-offset: 3px; +` + +export const SiteNameInput = styled(Input)` + width: 12rem; +` + +/** Compact readout of the current grid density (columns × rows). */ +export const DensityReadout = styled.span` + font-size: 13px; + min-width: 56px; + text-align: center; +` diff --git a/packages/dm-core-plugins/src/builder/components/BuilderToolbar.tsx b/packages/dm-core-plugins/src/builder/components/BuilderToolbar.tsx new file mode 100644 index 000000000..8a4627d9b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/BuilderToolbar.tsx @@ -0,0 +1,250 @@ +import { Button, Icon, Tooltip } from '@equinor/eds-core-react' +import { MAX_GRID_CELLS, MIN_GRID_CELLS } from '../model/model' +import * as Styled from '../styles' +import type { + TBuilderMode, + TBuilderModel, + TBuilderPluginConfig, +} from '../types' +import { ICONS } from '../utils/icons' +import * as S from './BuilderToolbar.styles' +import { DENSITY_STEP } from './Canvas' +import { TemplatesMenu } from './TemplatesMenu' + +export type TDevice = 'desktop' | 'tablet' | 'mobile' + +export const DEVICE_WIDTHS: Record = { + desktop: '100%', + tablet: '768px', + mobile: '380px', +} + +const DEVICE_ICONS: Record = { + desktop: 'desktop_mac', + tablet: 'tablet_android', + mobile: 'phone', +} + +type BuilderToolbarProps = { + config?: TBuilderPluginConfig + mode: TBuilderMode + locked: boolean + siteTitle: string + published: boolean + saveState: 'saved' | 'saving' | 'dirty' + saveStatusLabel: string + device: TDevice + gridColumns: number + gridRows: number + showJson: boolean + canUndo: boolean + canRedo: boolean + onSetMode: (mode: TBuilderMode) => void + onRenameSite: (title: string) => void + onCommit: () => void + onUndo: () => void + onRedo: () => void + onTogglePublished: () => void + onApplyTemplate: (build: () => TBuilderModel) => void + onSave: () => void + onSetDevice: (device: TDevice) => void + onDensity: (factor: number) => void + onToggleJson: () => void +} + +/** + * The builder's top toolbar: back link, site name/publish controls, undo/redo, + * templates, save status, device widths, grid density and view switches. + * + * Purely presentational — every action is delegated to the container via props. + */ +export const BuilderToolbar = ({ + config, + mode, + locked, + siteTitle, + published, + saveState, + saveStatusLabel, + device, + gridColumns, + gridRows, + showJson, + canUndo, + canRedo, + onSetMode, + onRenameSite, + onCommit, + onUndo, + onRedo, + onTogglePublished, + onApplyTemplate, + onSave, + onSetDevice, + onDensity, + onToggleJson, +}: BuilderToolbarProps): React.ReactElement => ( + + + { + if (config?.directoryUrl) { + window.location.assign(config.directoryUrl) + } else { + window.history.back() + } + }} + > + Website builder + + {mode === 'edit' && ( + + ) => + onRenameSite(event.target.value) + } + onBlur={onCommit} + /> + + )} + {mode === 'edit' && !locked && ( + + + + {published ? 'Published' : 'Publish'} + + + )} + {mode === 'edit' && ( + onApplyTemplate(template.build)} + /> + )} + {mode === 'edit' && ( + <> + + + + + + + + + + + > + )} + + + {mode === 'edit' && ( + + {saveStatusLabel} + + )} + {mode === 'edit' && ( + + + Save + + )} + {(['desktop', 'tablet', 'mobile'] as TDevice[]).map((value) => ( + onSetDevice(value)} + > + + + ))} + {mode === 'edit' && ( + <> + + onDensity(1 / DENSITY_STEP)} + > + + + + + + {gridColumns}×{gridRows} + + + + = MAX_GRID_CELLS && gridRows >= MAX_GRID_CELLS + } + onClick={() => onDensity(DENSITY_STEP)} + > + + + + > + )} + + Advanced: JSON + + onSetMode('edit')} + > + + Edit + + onSetMode('preview')} + > + + Preview + + + +) diff --git a/packages/dm-core-plugins/src/builder/components/Canvas.styles.ts b/packages/dm-core-plugins/src/builder/components/Canvas.styles.ts new file mode 100644 index 000000000..3284a474f --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Canvas.styles.ts @@ -0,0 +1,61 @@ +import { Icon } from '@equinor/eds-core-react' +import { tokens } from '@equinor/eds-tokens' +import type { CSSProperties } from 'react' +import styled from 'styled-components' +import * as Styled from '../styles' + +const colorPrimary = tokens.colors.interactive.primary__resting.hex + +export const CanvasItem = styled(Styled.CanvasItem)<{ + $gridArea: string + $transform?: string +}>` + grid-area: ${(props) => props.$gridArea}; + transform: ${(props) => props.$transform}; +` + +export const BlockIcon = styled(Icon)` + margin-right: 4px; + vertical-align: middle; +` + +export const CanvasItemBody = styled(Styled.CanvasItemBody)<{ + $alignItems: CSSProperties['alignItems'] + $justifyContent: CSSProperties['justifyContent'] +}>` + flex-direction: column; + align-items: ${(props) => props.$alignItems}; + justify-content: ${(props) => props.$justifyContent}; +` + +export const ItemTitle = styled.div<{ + $textAlign?: CSSProperties['textAlign'] + $fontSize: string + $fontWeight: number + $color: string + $padding?: CSSProperties['padding'] +}>` + text-align: ${(props) => props.$textAlign}; + font-size: ${(props) => props.$fontSize}; + font-weight: ${(props) => props.$fontWeight}; + color: ${(props) => props.$color}; + padding: ${(props) => props.$padding}; +` + +export const ItemBodyText = styled.div<{ + $textAlign?: CSSProperties['textAlign'] + $fontSize?: string + $fontWeight?: number + $color?: string + $padding?: CSSProperties['padding'] +}>` + text-align: ${(props) => props.$textAlign}; + font-size: ${(props) => props.$fontSize}; + font-weight: ${(props) => props.$fontWeight}; + color: ${(props) => props.$color}; + padding: ${(props) => props.$padding}; +` + +export const CanvasGrid = styled(Styled.CanvasGrid)<{ $isOver: boolean }>` + outline: ${(props) => (props.$isOver ? `2px dashed ${colorPrimary}` : 'none')}; +` diff --git a/packages/dm-core-plugins/src/builder/components/Canvas.tsx b/packages/dm-core-plugins/src/builder/components/Canvas.tsx new file mode 100644 index 000000000..8d18dee93 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Canvas.tsx @@ -0,0 +1,355 @@ +import { useDndMonitor, useDraggable, useDroppable } from '@dnd-kit/core' +import { Button, Icon } from '@equinor/eds-core-react' +import { + type ReactNode, + type PointerEvent as ReactPointerEvent, + useEffect, + useRef, +} from 'react' +import type { TGridArea, TGridItem } from '../../grid/types' +import { getBlock } from '../model/blocks' +import { isContainerItem } from '../model/model' +import * as Styled from '../styles' +import type { TBuilderModel } from '../types' +import { pixelDeltaToCells } from '../utils/gridMetrics' +import { ICONS } from '../utils/icons' +import * as S from './Canvas.styles' + +type TGridMetrics = { size: TBuilderModel['size']; rect: () => DOMRect | null } + +/** Multiplicative density step for one zoom increment (in/out). */ +export const DENSITY_STEP = 1.5 + +const CanvasItem = ({ + item, + index, + selected, + metrics, + onSelect, + onDelete, + onDuplicate, + onResize, + onResizeEnd, + onEnter, +}: { + item: TGridItem + index: number + selected: boolean + metrics: TGridMetrics + onSelect: (index: number) => void + onDelete: (index: number) => void + onDuplicate: (index: number) => void + onResize: (index: number, area: TGridArea) => void + onResizeEnd: () => void + onEnter: (index: number) => void +}) => { + const recipe = item.viewConfig.recipe + const blockId = typeof recipe === 'string' ? recipe : recipe?.name + const block = getBlock(String(blockId)) + const isContainer = isContainerItem(item) + const { gridArea } = item + + const { attributes, listeners, setNodeRef, transform, isDragging } = + useDraggable({ id: `item:${index}`, data: { type: 'canvas-item', index } }) + + // Live resize via native pointer events on the corner handle. Kept separate + // from dnd-kit (which owns moving) so the two gestures never conflict. + const resizeOrigin = useRef(null) + const resizeStart = useRef<{ x: number; y: number } | null>(null) + + const handleResizePointerDown = (event: ReactPointerEvent) => { + event.preventDefault() + event.stopPropagation() + resizeOrigin.current = gridArea + resizeStart.current = { x: event.clientX, y: event.clientY } + event.currentTarget.setPointerCapture(event.pointerId) + } + + const handleResizePointerMove = (event: ReactPointerEvent) => { + const origin = resizeOrigin.current + const start = resizeStart.current + const rect = metrics.rect() + if (!origin || !start || !rect) return + const delta = pixelDeltaToCells( + { x: event.clientX - start.x, y: event.clientY - start.y }, + rect, + metrics.size + ) + onResize(index, { + columnStart: origin.columnStart, + rowStart: origin.rowStart, + columnEnd: origin.columnEnd + delta.columns, + rowEnd: origin.rowEnd + delta.rows, + }) + } + + const handleResizePointerUp = (event: ReactPointerEvent) => { + const wasResizing = resizeOrigin.current !== null + resizeOrigin.current = null + resizeStart.current = null + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId) + } + if (wasResizing) onResizeEnd() + } + + const childCount = + isContainer && typeof recipe === 'object' + ? (recipe?.config?.items?.length ?? 0) + : 0 + + const bodyText = + typeof recipe === 'object' && typeof recipe?.config?.content === 'string' + ? recipe.config.content + : undefined + const gridAreaValue = `${gridArea.rowStart} / ${gridArea.columnStart} / ${ + gridArea.rowEnd + 1 + } / ${gridArea.columnEnd + 1}` + const transformValue = transform + ? `translate3d(${transform.x}px, ${transform.y}px, 0)` + : undefined + const itemAlign = + item.style?.textAlign === 'center' + ? 'center' + : item.style?.textAlign === 'right' + ? 'flex-end' + : item.style?.textAlign === 'left' + ? 'flex-start' + : 'stretch' + const itemJustify = + item.style?.verticalAlign === 'center' + ? 'center' + : item.style?.verticalAlign === 'bottom' + ? 'flex-end' + : item.style?.verticalAlign === 'top' + ? 'flex-start' + : isContainer + ? 'center' + : 'flex-start' + + return ( + { + event.stopPropagation() + onSelect(index) + }} + onDoubleClick={(event) => { + if (!isContainer) return + event.stopPropagation() + onEnter(index) + }} + > + + + {block && } + {item.title ?? block?.label ?? 'Widget'} + + event.stopPropagation()} + > + {isContainer && ( + { + event.stopPropagation() + onEnter(index) + }} + > + + + )} + { + event.stopPropagation() + onDuplicate(index) + }} + > + + + { + event.stopPropagation() + onDelete(index) + }} + > + + + + + + {isContainer ? ( + `${childCount} widget${childCount === 1 ? '' : 's'} — double-click or open to edit` + ) : ( + <> + {item.title && !block?.hideTitle && ( + + {item.title} + + )} + + {bodyText ?? block?.description} + + > + )} + + event.stopPropagation()} + onPointerDown={handleResizePointerDown} + onPointerMove={handleResizePointerMove} + onPointerUp={handleResizePointerUp} + onPointerCancel={handleResizePointerUp} + /> + + ) +} + +export const Canvas = ({ + model, + selectedIndex, + frameWidth, + breadcrumb, + onSelect, + onDelete, + onDuplicate, + onMove, + onResize, + onResizeEnd, + onEnter, + onDensity, + nav, + navbar, +}: { + model: TBuilderModel + selectedIndex: number | null + frameWidth: string + breadcrumb?: ReactNode + onSelect: (index: number | null) => void + onDelete: (index: number) => void + onDuplicate: (index: number) => void + onMove: (index: number, deltaColumns: number, deltaRows: number) => void + onResize: (index: number, area: TGridArea) => void + onResizeEnd: () => void + onEnter: (index: number) => void + onDensity: (factor: number) => void + nav?: ReactNode + navbar?: ReactNode +}): React.ReactElement => { + const { setNodeRef, isOver } = useDroppable({ id: 'canvas' }) + const gridRef = useRef(null) + const panelRef = useRef(null) + + // Translate a finished canvas-item drag (pixels) into a grid cell move. + useDndMonitor({ + onDragEnd(event) { + const data = event.active.data.current + if (data?.type !== 'canvas-item') return + const rect = gridRef.current?.getBoundingClientRect() + if (!rect) return + const delta = pixelDeltaToCells(event.delta, rect, model.size) + if (delta.columns !== 0 || delta.rows !== 0) { + onMove(data.index as number, delta.columns, delta.rows) + } + }, + }) + + // Ctrl/Cmd + wheel adjusts grid density (zoom). A native, non-passive + // listener is required so we can call preventDefault to suppress page zoom. + // Wheel deltas are accumulated so trackpads take one density step per gesture + // chunk rather than dozens. + useEffect(() => { + const node = panelRef.current + if (!node) return + let accumulated = 0 + const THRESHOLD = 40 + const onWheel = (event: WheelEvent) => { + if (!event.ctrlKey && !event.metaKey) return + event.preventDefault() + accumulated += event.deltaY + while (accumulated <= -THRESHOLD) { + accumulated += THRESHOLD + onDensity(DENSITY_STEP) + } + while (accumulated >= THRESHOLD) { + accumulated -= THRESHOLD + onDensity(1 / DENSITY_STEP) + } + } + node.addEventListener('wheel', onWheel, { passive: false }) + return () => node.removeEventListener('wheel', onWheel) + }, [onDensity]) + + const metrics: TGridMetrics = { + size: model.size, + rect: () => gridRef.current?.getBoundingClientRect() ?? null, + } + + return ( + onSelect(null)}> + {breadcrumb} + + {navbar} + + {nav} + + + { + setNodeRef(node) + gridRef.current = node + }} + $size={model.size} + $editing + $isOver={isOver} + > + {model.items.length === 0 && ( + + Drag a widget here, or click one in the palette, to start + building your page. + + )} + {model.items.map((item, index) => ( + + ))} + + + + + + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/ImageUploadField.styles.ts b/packages/dm-core-plugins/src/builder/components/ImageUploadField.styles.ts new file mode 100644 index 000000000..09d700b41 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/ImageUploadField.styles.ts @@ -0,0 +1,15 @@ +import { Button } from '@equinor/eds-core-react' +import styled from 'styled-components' +import * as Styled from '../styles' + +export const HiddenInput = styled.input` + display: none; +` + +export const RemoveButton = styled(Button)` + margin-top: 4px; +` + +export const ErrorHelp = styled(Styled.FieldHelp)` + color: #c00; +` diff --git a/packages/dm-core-plugins/src/builder/components/ImageUploadField.tsx b/packages/dm-core-plugins/src/builder/components/ImageUploadField.tsx new file mode 100644 index 000000000..5900e3aea --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/ImageUploadField.tsx @@ -0,0 +1,101 @@ +import { + EBlueprint, + type TFileEntity, + useApplication, +} from '@development-framework/dm-core' +import { Button, Progress } from '@equinor/eds-core-react' +import { type ChangeEvent, useRef, useState } from 'react' +import * as Styled from '../styles' +import * as S from './ImageUploadField.styles' + +type TImageUploadFieldProps = { + label: string + /** Current image address (absolute DMSS File address) or empty. */ + value: string + dataSource: string + onChange: (address: string | undefined) => void +} + +/** + * Lets the user pick an image from their computer. The blob is uploaded to the + * data source and a File entity is created so the media viewer can render it. + * The widget stores only the resulting File address, so it works for any number + * of images without needing a matching page attribute. + */ +export const ImageUploadField = ({ + label, + value, + dataSource, + onChange, +}: TImageUploadFieldProps): React.ReactElement => { + const input = useRef(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState() + const { dmssAPI } = useApplication() + + const handleFile = async (event: ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + setError(undefined) + setLoading(true) + try { + const blobId = crypto.randomUUID() + await dmssAPI.blobUpload({ dataSourceId: dataSource, file, blobId }) + const ext = file.name.split('.').slice(1).pop()?.toLowerCase() ?? '' + const entity: TFileEntity = { + type: EBlueprint.FILE, + name: file.name.split('.').slice(0, -1).join('.') || file.name, + author: '', + date: new Date().toDateString(), + size: file.size, + filetype: ext, + content: { + type: 'dmss://system/SIMOS/Reference', + referenceType: 'storage', + address: `$${blobId}`, + }, + } + const res = await dmssAPI.documentAddSimple({ + dataSourceId: dataSource, + body: entity, + }) + onChange(`dmss://${dataSource}/$${res.data}`) + } catch { + setError('Could not upload image') + } finally { + setLoading(false) + if (input.current) input.current.value = '' + } + } + + return ( + <> + {label} + + input.current?.click()} + > + {loading ? ( + + ) : value ? ( + 'Replace image…' + ) : ( + 'Browse computer…' + )} + + {value && ( + onChange(undefined)}> + Remove image + + )} + {error && {error}} + > + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/Inspector.tsx b/packages/dm-core-plugins/src/builder/components/Inspector.tsx new file mode 100644 index 000000000..954709cf9 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Inspector.tsx @@ -0,0 +1,456 @@ +import { + NativeSelect, + Switch, + TextField, + Typography, +} from '@equinor/eds-core-react' +import { useEffect, useState } from 'react' +import type { TGridArea, TGridItem, TGridSize } from '../../grid/types' +import { getWidgetConfigValue } from '../model/model' +import * as Styled from '../styles' +import type { TBlock, TInspectorField } from '../types' +import { ImageUploadField } from './ImageUploadField' +import { TableSourceField } from './TableSourceField' + +export type TInspectorHandlers = { + onTitle: (value: string) => void + onLabel: (value: string) => void + onScope: (value: string) => void + onArea: (area: TGridArea) => void + onConfigValue: (key: string, value: unknown) => void + onStyleValue: ( + key: keyof NonNullable, + value: unknown, + target: 'body' | 'title' + ) => void + /** End the current coalescing run (called on blur) so each edit is one undo. */ + onCommit: () => void +} + +type TInspectorProps = TInspectorHandlers & { + item: TGridItem | null + block: TBlock | undefined + gridSize: TGridSize + dataSource: string +} + +const areaToBox = (area: TGridArea) => ({ + x: area.columnStart, + y: area.rowStart, + width: area.columnEnd - area.columnStart + 1, + height: area.rowEnd - area.rowStart + 1, +}) + +const boxToArea = (box: { + x: number + y: number + width: number + height: number +}): TGridArea => ({ + columnStart: box.x, + columnEnd: box.x + Math.max(1, box.width) - 1, + rowStart: box.y, + rowEnd: box.y + Math.max(1, box.height) - 1, +}) + +/** + * Numeric layout field that keeps a local draft so the input can be cleared + * while editing. Valid entries commit to the model (clamped to >= 1); an empty + * or invalid draft snaps back to the model value on blur. + */ +const LayoutNumberField = ({ + id, + label, + value, + onChangeValue, +}: { + id: string + label: string + value: number + onChangeValue: (next: number) => void +}): React.ReactElement => { + const [draft, setDraft] = useState(String(value)) + + // Resync when the value changes outside this field (canvas drag-resize, undo). + useEffect(() => { + setDraft(String(value)) + }, [value]) + + return ( + ) => { + setDraft(event.target.value) + const parsed = Number.parseInt(event.target.value, 10) + if (!Number.isNaN(parsed)) onChangeValue(Math.max(1, parsed)) + }} + onBlur={() => setDraft(String(value))} + /> + ) +} + +const FieldControl = ({ + field, + value, + dataSource, + onChange, +}: { + field: TInspectorField + value: unknown + dataSource: string + onChange: (value: unknown) => void +}) => { + const id = `inspector-${field.target.kind}-${ + field.target.kind === 'config' ? field.target.key : field.label + }` + + if (field.type === 'image-upload') { + return ( + onChange(address)} + /> + ) + } + + if (field.type === 'table-source') { + return ( + onChange(rows)} + /> + ) + } + + if (field.type === 'boolean') { + return ( + onChange(event.target.checked)} + /> + ) + } + + if (field.type === 'select') { + const options = field.options ?? [] + const isNumeric = options.some((option) => typeof option.value === 'number') + return ( + + onChange(isNumeric ? Number(event.target.value) : event.target.value) + } + > + {options.map((option) => ( + + {option.label} + + ))} + + ) + } + + if (field.type === 'textarea') { + return ( + <> + {field.label} + onChange(event.target.value)} + /> + > + ) + } + + return ( + ) => { + if (field.type !== 'number') { + onChange(event.target.value) + return + } + if (event.target.value === '') { + onChange(undefined) + return + } + const parsed = Number(event.target.value) + // Ignore intermediate/invalid numeric input (e.g. "-", "1e") so NaN + // never enters the model. + if (Number.isFinite(parsed)) onChange(parsed) + }} + /> + ) +} + +const StyleGroup = ({ + title, + style, + onChange, +}: { + title: string + style: TGridItem['style'] + onChange: (key: keyof NonNullable, value: unknown) => void +}) => ( + + {title} + + + onChange('textAlign', event.target.value || undefined) + } + > + Default + Left + Center + Right + + + + + onChange('verticalAlign', event.target.value || undefined) + } + > + Default + Top + Center + Bottom + + + + + onChange('fontSize', event.target.value || undefined) + } + > + Default + Small (12px) + Normal (16px) + Large (20px) + X-Large (28px) + Heading (40px) + + + + onChange('bold', event.target.checked)} + /> + + + ) => + onChange('color', event.target.value || undefined) + } + /> + + + + onChange('padding', event.target.value || undefined) + } + > + None + Small (8px) + Medium (16px) + Large (32px) + + + +) + +export const Inspector = ({ + item, + block, + gridSize, + dataSource, + onTitle, + onLabel, + onScope, + onArea, + onConfigValue, + onStyleValue, + onCommit, +}: TInspectorProps): React.ReactElement => { + if (!item) { + return ( + + Properties + + Select a widget on the canvas to edit its properties. + + + ) + } + + const box = areaToBox(item.gridArea) + const updateBox = (patch: Partial) => + onArea(boxToArea({ ...box, ...patch })) + + return ( + // Committing on blur ends the per-field coalescing run, so each field edit + // becomes a single undo step. + + + {block?.label ?? 'Widget'} properties + + + + General + + ) => + onTitle(event.target.value) + } + /> + + + ) => + onLabel(event.target.value) + } + /> + + + + + Layout + + updateBox({ x: next })} + /> + updateBox({ y: next })} + /> + updateBox({ width: next })} + /> + updateBox({ height: next })} + /> + + + Grid is {gridSize.columns} columns × {gridSize.rows} rows. + + + + + + Data binding + + + ) => + onScope(event.target.value) + } + /> + + Attribute path this widget binds to on the document. + + + + + onStyleValue(key, value, 'title')} + /> + + onStyleValue(key, value, 'body')} + /> + + {block?.fields && block.fields.length > 0 && ( + + Settings + {block.fields.map((field) => { + const value = + field.target.kind === 'scope' + ? item.viewConfig.scope + : field.target.kind === 'label' + ? item.viewConfig.label + : getWidgetConfigValue(item, field.target.key) + return ( + + { + if (field.target.kind === 'scope') + onScope(String(next ?? '')) + else if (field.target.kind === 'label') + onLabel(String(next ?? '')) + else onConfigValue(field.target.key, next) + }} + /> + {field.help && ( + {field.help} + )} + + ) + })} + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/NavSidebar.styles.ts b/packages/dm-core-plugins/src/builder/components/NavSidebar.styles.ts new file mode 100644 index 000000000..1870b0c38 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/NavSidebar.styles.ts @@ -0,0 +1,21 @@ +import { tokens } from '@equinor/eds-tokens' +import styled from 'styled-components' +import * as Styled from '../styles' + +export const NavItem = styled(Styled.NavItem)<{ $depth: number }>` + padding-left: ${(props) => 8 + props.$depth * 14}px; +` + +export const IconButton = styled.button<{ $active: boolean }>` + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px; + border: none; + background: transparent; + cursor: pointer; + color: ${(props) => + props.$active + ? tokens.colors.text.static_icons__primary_white.hex + : tokens.colors.text.static_icons__tertiary.hex}; +` diff --git a/packages/dm-core-plugins/src/builder/components/NavSidebar.tsx b/packages/dm-core-plugins/src/builder/components/NavSidebar.tsx new file mode 100644 index 000000000..28e9175a4 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/NavSidebar.tsx @@ -0,0 +1,244 @@ +import { Icon } from '@equinor/eds-core-react' +import { useEffect, useRef, useState } from 'react' +import type { TBuilderPage } from '../model/site' +import * as Styled from '../styles' +import { ICONS } from '../utils/icons' +import * as S from './NavSidebar.styles' + +/** Ids of every ancestor of `targetId` within `pages` (root-most first). */ +const ancestorIds = (pages: TBuilderPage[], targetId: string): string[] => { + const walk = (siblings: TBuilderPage[], trail: string[]): string[] | null => { + for (const page of siblings) { + if (page.id === targetId) return trail + const found = walk(page.children, [...trail, page.id]) + if (found) return found + } + return null + } + return walk(pages, []) ?? [] +} + +type TDrag = { parentId: string | null; index: number } + +type TNavSidebarProps = { + pages: TBuilderPage[] + activePageId: string + editing: boolean + onNavigate: (id: string) => void + onAddPage: (parentId: string | null) => void + onRenamePage: (id: string, title: string) => void + onDeletePage: (id: string) => void + onReorder: (parentId: string | null, from: number, to: number) => void +} + +/** + * The website's own left navigation, rendered as an infinitely nestable menu + * tree in both edit and preview so the canvas is WYSIWYG. + * + * - Preview: clicking a page navigates to it (client-side, SPA-style); pages + * with sub-pages can be expanded to reveal their nested menu. + * - Edit: clicking a page switches the canvas to that page; pages can be added + * (as top-level pages or sub-pages of any page), renamed inline, deleted, and + * reordered by dragging within their level. + */ +export const NavSidebar = ({ + pages, + activePageId, + editing, + onNavigate, + onAddPage, + onRenamePage, + onDeletePage, + onReorder, +}: TNavSidebarProps): React.ReactElement => { + const [renamingId, setRenamingId] = useState(null) + const [draftTitle, setDraftTitle] = useState('') + const [collapsed, setCollapsed] = useState>(new Set()) + const [drag, setDrag] = useState(null) + const inputRef = useRef(null) + + useEffect(() => { + if (renamingId) inputRef.current?.focus() + }, [renamingId]) + + // Keep the active page reachable by expanding all of its ancestors. + useEffect(() => { + const ancestors = ancestorIds(pages, activePageId) + if (ancestors.length === 0) return + setCollapsed((current) => { + if (!ancestors.some((id) => current.has(id))) return current + const next = new Set(current) + for (const id of ancestors) next.delete(id) + return next + }) + }, [pages, activePageId]) + + const startRename = (page: TBuilderPage) => { + setRenamingId(page.id) + setDraftTitle(page.title) + } + + const commitRename = () => { + if (renamingId) { + const title = draftTitle.trim() + if (title) onRenamePage(renamingId, title) + } + setRenamingId(null) + } + + const toggleCollapsed = (id: string) => + setCollapsed((current) => { + const next = new Set(current) + if (next.has(id)) next.delete(id) + else next.add(id) + return next + }) + + const renderPages = ( + siblings: TBuilderPage[], + parentId: string | null, + depth: number + ): React.ReactElement[] => + siblings.flatMap((page, index) => { + const active = page.id === activePageId + const isRenaming = renamingId === page.id + const hasChildren = page.children.length > 0 + const expanded = hasChildren && !collapsed.has(page.id) + + const row = ( + { + if (!isRenaming) onNavigate(page.id) + }} + onDragOver={(event) => { + if (!editing || !drag || drag.parentId !== parentId) return + event.preventDefault() + }} + onDrop={(event) => { + if (!editing || !drag || drag.parentId !== parentId) return + event.preventDefault() + if (drag.index !== index) onReorder(parentId, drag.index, index) + setDrag(null) + }} + > + {editing && !isRenaming && ( + event.stopPropagation()} + onDragStart={() => setDrag({ parentId, index })} + onDragEnd={() => setDrag(null)} + aria-label={`Reorder ${page.title}`} + > + + + )} + + {hasChildren ? ( + { + event.stopPropagation() + toggleCollapsed(page.id) + }} + > + + + ) : ( + + )} + + {isRenaming ? ( + event.stopPropagation()} + onChange={(event) => setDraftTitle(event.target.value)} + onBlur={commitRename} + onKeyDown={(event) => { + if (event.key === 'Enter') commitRename() + if (event.key === 'Escape') setRenamingId(null) + }} + /> + ) : ( + {page.title} + )} + + {editing && !isRenaming && ( + + { + event.stopPropagation() + setCollapsed((current) => { + const next = new Set(current) + next.delete(page.id) + return next + }) + onAddPage(page.id) + }} + > + + + { + event.stopPropagation() + startRename(page) + }} + > + + + {!(parentId === null && pages.length <= 1) && ( + { + event.stopPropagation() + onDeletePage(page.id) + }} + > + + + )} + + )} + + ) + + return expanded + ? [row, ...renderPages(page.children, page.id, depth + 1)] + : [row] + }) + + return ( + + + {editing ? 'Pages' : 'Menu'} + + + {renderPages(pages, null, 0)} + + {editing && ( + onAddPage(null)}> + + Add page + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/Navbar.styles.ts b/packages/dm-core-plugins/src/builder/components/Navbar.styles.ts new file mode 100644 index 000000000..71359b2dc --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Navbar.styles.ts @@ -0,0 +1,37 @@ +import styled from 'styled-components' +import * as Styled from '../styles' + +export const NavbarBrand = styled(Styled.NavbarBrand)<{ $editing: boolean }>` + cursor: ${(props) => (props.$editing ? 'text' : 'default')}; +` + +export const LabelButton = styled.button` + font: inherit; + font-size: 14px; + color: #3d3d3d; + background: transparent; + border: none; + cursor: text; + padding: 2px 4px; +` + +export const IconButton = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px; + border: none; + background: transparent; + cursor: pointer; + color: inherit; +` + +export const RemoveButton = styled.button` + padding: 6px 8px; + border: 1px solid #d6534f; + border-radius: 5px; + background: transparent; + cursor: pointer; + font-size: 13px; + color: #d6534f; +` diff --git a/packages/dm-core-plugins/src/builder/components/Navbar.tsx b/packages/dm-core-plugins/src/builder/components/Navbar.tsx new file mode 100644 index 000000000..aaff8f42e --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Navbar.tsx @@ -0,0 +1,386 @@ +import { Icon } from '@equinor/eds-core-react' +import { useEffect, useRef, useState } from 'react' +import type { + TBuilderPage, + TNavbar, + TNavbarItem, + TNavbarItemTarget, +} from '../model/site' +import * as Styled from '../styles' +import { ICONS } from '../utils/icons' +import * as S from './Navbar.styles' + +/** Flatten the page tree to `{ id, label }` options for the target selector. */ +const flattenPages = ( + pages: TBuilderPage[], + depth = 0 +): { id: string; label: string }[] => + pages.flatMap((page) => [ + { id: page.id, label: `${'\u00A0\u00A0'.repeat(depth)}${page.title}` }, + ...flattenPages(page.children, depth + 1), + ]) + +type TNavbarProps = { + navbar: TNavbar + pages: TBuilderPage[] + editing: boolean + onNavigate: (pageId: string) => void + onUpdateNavbar: (patch: Partial) => void + onAddItem: () => void + onUpdateItem: (id: string, patch: Partial) => void + onRemoveItem: (id: string) => void + onReorderItem: (from: number, to: number) => void +} + +/** + * The site's customizable full-width top navbar, shared by every page and + * rendered in both edit and preview so the canvas stays WYSIWYG. + * + * - Preview: brand text plus clickable links. Page links navigate within the + * site (SPA-style); URL links open in a new tab. + * - Edit: the brand is editable inline, each link can be renamed, re-targeted + * (to a page or an external URL), deleted, and reordered by dragging; a + * settings popover exposes colors, alignment, and disabling the navbar. + * - Disabled: in edit mode an "Add navbar" affordance lets the author opt in; + * in preview the navbar renders nothing so existing sites are unchanged. + */ +export const Navbar = ({ + navbar, + pages, + editing, + onNavigate, + onUpdateNavbar, + onAddItem, + onUpdateItem, + onRemoveItem, + onReorderItem, +}: TNavbarProps): React.ReactElement | null => { + const [editingBrand, setEditingBrand] = useState(false) + const [brandDraft, setBrandDraft] = useState(navbar.brand) + const [renamingId, setRenamingId] = useState(null) + const [labelDraft, setLabelDraft] = useState('') + const [settingsOpen, setSettingsOpen] = useState(false) + const [dragIndex, setDragIndex] = useState(null) + const brandRef = useRef(null) + const labelRef = useRef(null) + const settingsRef = useRef(null) + const settingsPanelRef = useRef(null) + + useEffect(() => { + if (editingBrand) brandRef.current?.focus() + }, [editingBrand]) + + useEffect(() => { + if (renamingId) labelRef.current?.focus() + }, [renamingId]) + + // Move focus into the settings popover when it opens so keyboard users land + // inside it (and Escape-to-close works). + useEffect(() => { + if (settingsOpen) settingsPanelRef.current?.focus() + }, [settingsOpen]) + + // Close the settings popover when clicking outside of it. + useEffect(() => { + if (!settingsOpen) return + const onPointerDown = (event: PointerEvent) => { + if (!settingsRef.current?.contains(event.target as Node)) { + setSettingsOpen(false) + } + } + document.addEventListener('pointerdown', onPointerDown) + return () => document.removeEventListener('pointerdown', onPointerDown) + }, [settingsOpen]) + + if (!navbar.enabled) { + if (!editing) return null + return ( + onUpdateNavbar({ enabled: true })} + > + + Add a top navbar + + ) + } + + const pageOptions = flattenPages(pages) + + const commitBrand = () => { + const brand = brandDraft.trim() + if (brand) onUpdateNavbar({ brand }) + else setBrandDraft(navbar.brand) + setEditingBrand(false) + } + + const startRename = (item: TNavbarItem) => { + setRenamingId(item.id) + setLabelDraft(item.label) + } + + const commitRename = () => { + if (renamingId) { + const label = labelDraft.trim() + if (label) onUpdateItem(renamingId, { label }) + } + setRenamingId(null) + } + + const changeTargetKind = ( + item: TNavbarItem, + kind: TNavbarItemTarget['kind'] + ) => { + if (kind === item.target.kind) return + const target: TNavbarItemTarget = + kind === 'url' + ? { kind: 'url', href: '' } + : { kind: 'page', pageId: pages[0]?.id ?? '' } + onUpdateItem(item.id, { target }) + } + + const activate = (item: TNavbarItem) => { + if (item.target.kind === 'page') { + if (item.target.pageId) onNavigate(item.target.pageId) + } else if (item.target.href) { + window.open(item.target.href, '_blank', 'noopener,noreferrer') + } + } + + return ( + + {editing && editingBrand ? ( + setBrandDraft(event.target.value)} + onBlur={commitBrand} + onKeyDown={(event) => { + if (event.key === 'Enter') commitBrand() + if (event.key === 'Escape') { + setBrandDraft(navbar.brand) + setEditingBrand(false) + } + }} + /> + ) : ( + { + if (editing) { + setBrandDraft(navbar.brand) + setEditingBrand(true) + } + }} + title={editing ? 'Click to edit brand' : undefined} + > + {navbar.brand} + + )} + + + {navbar.items.map((item, index) => { + if (!editing) { + return ( + activate(item)} + > + {item.label} + {item.target.kind === 'url' && ( + + )} + + ) + } + + const isRenaming = renamingId === item.id + return ( + { + if (dragIndex === null) return + event.preventDefault() + }} + onDrop={(event) => { + if (dragIndex === null) return + event.preventDefault() + if (dragIndex !== index) onReorderItem(dragIndex, index) + setDragIndex(null) + }} + > + setDragIndex(index)} + onDragEnd={() => setDragIndex(null)} + aria-label={`Reorder ${item.label}`} + > + + + + {isRenaming ? ( + setLabelDraft(event.target.value)} + onBlur={commitRename} + onKeyDown={(event) => { + if (event.key === 'Enter') commitRename() + if (event.key === 'Escape') setRenamingId(null) + }} + /> + ) : ( + startRename(item)} + title='Rename link' + > + {item.label} + + )} + + { + const value = event.target.value + if (value === '__url__') { + changeTargetKind(item, 'url') + } else { + onUpdateItem(item.id, { + target: { kind: 'page', pageId: value }, + }) + } + }} + > + {pageOptions.map((option) => ( + + {option.label} + + ))} + External URL… + + + {item.target.kind === 'url' && ( + + onUpdateItem(item.id, { + target: { kind: 'url', href: event.target.value }, + }) + } + /> + )} + + onRemoveItem(item.id)} + > + + + + ) + })} + + {editing && ( + + + Add link + + )} + + + {editing && ( + + setSettingsOpen((open) => !open)} + > + + + + {settingsOpen && ( + { + if (event.key === 'Escape') setSettingsOpen(false) + }} + > + + Background + + onUpdateNavbar({ background: event.target.value }) + } + /> + + + Text color + + onUpdateNavbar({ color: event.target.value }) + } + /> + + + Brand color + + onUpdateNavbar({ brandColor: event.target.value }) + } + /> + + + Align + + onUpdateNavbar({ + align: event.target.value as TNavbar['align'], + }) + } + > + Left + Center + Right + + + { + onUpdateNavbar({ enabled: false }) + setSettingsOpen(false) + }} + > + Remove navbar + + + )} + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/Outline.tsx b/packages/dm-core-plugins/src/builder/components/Outline.tsx new file mode 100644 index 000000000..e55142d4b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Outline.tsx @@ -0,0 +1,81 @@ +import { Button, Icon, Typography } from '@equinor/eds-core-react' +import type { TGridItem } from '../../grid/types' +import { getBlock } from '../model/blocks' +import { isContainerItem } from '../model/model' +import * as Styled from '../styles' +import { ICONS } from '../utils/icons' + +const itemLabel = (item: TGridItem): string => { + const recipe = item.viewConfig.recipe + const blockId = typeof recipe === 'string' ? recipe : recipe?.name + const block = getBlock(String(blockId)) + return item.title ?? item.viewConfig.label ?? block?.label ?? 'Widget' +} + +const itemIcon = (item: TGridItem): string => { + const recipe = item.viewConfig.recipe + const blockId = typeof recipe === 'string' ? recipe : recipe?.name + return getBlock(String(blockId))?.icon ?? 'view_module' +} + +/** + * A flat list of the widgets in the grid currently being edited. Clicking a row + * selects that widget; container widgets can be opened to drill into their + * nested grid (mirroring the canvas and breadcrumb). + */ +export const Outline = ({ + items, + selectedIndex, + onSelect, + onEnter, +}: { + items: TGridItem[] + selectedIndex: number | null + onSelect: (index: number) => void + onEnter: (index: number) => void +}): React.ReactElement => { + return ( + + Outline + {items.length === 0 ? ( + No widgets on this page yet. + ) : ( + + {items.map((item, index) => { + const container = isContainerItem(item) + const recipe = item.viewConfig.recipe + const childCount = + container && typeof recipe === 'object' + ? (recipe?.config?.items?.length ?? 0) + : 0 + return ( + onSelect(index)} + > + + {itemLabel(item)} + {container && ( + <> + {childCount} + { + event.stopPropagation() + onEnter(index) + }} + > + + + > + )} + + ) + })} + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/SiteCard.tsx b/packages/dm-core-plugins/src/builder/components/SiteCard.tsx new file mode 100644 index 000000000..abef0e5f5 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/SiteCard.tsx @@ -0,0 +1,94 @@ +import { Icon } from '@equinor/eds-core-react' +import { delete_to_trash, edit, external_link } from '@equinor/eds-icons' +import * as S from '../styles/directory.styles' + +export type TSiteHit = { + address: string + dataSource: string + id: string + name: string + /** Friendly display name; falls back to `name` (the slug) when unset. */ + label: string + /** Whether the site is published (live) or still a draft. */ + published: boolean +} + +type SiteCardProps = { + site: TSiteHit + viewUrl: string + editUrl: string | null + onDeleteRequest: () => void + deleting: boolean +} + +/** + * Derive a deterministic hue (0–360) from a string so every card gets its + * own colour without any runtime randomness that would shift on re-render. + */ +const hueFromString = (value: string): number => { + let hue = 0 + for (let i = 0; i < value.length; i++) + hue = (hue * 31 + value.charCodeAt(i)) & 0xffff + return hue % 360 +} + +/** A single site tile in the directory gallery. */ +export const SiteCard = ({ + site, + viewUrl, + editUrl, + onDeleteRequest, + deleting, +}: SiteCardProps): React.ReactElement => { + const initial = (site.label[0] ?? '?').toUpperCase() + + return ( + + {/* Coloured banner with the site's initial letter */} + + {initial} + {!site.published && Draft} + + + {/* Card body */} + + + {site.label} + + {site.dataSource} + + {/* Action row */} + + + + + Open + + + {editUrl ? ( + + + + + + ) : null} + + + + + + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/SiteGrid.tsx b/packages/dm-core-plugins/src/builder/components/SiteGrid.tsx new file mode 100644 index 000000000..a8f2b5e2f --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/SiteGrid.tsx @@ -0,0 +1,199 @@ +import { + ErrorBoundary, + useApplication, + ViewCreator, +} from '@development-framework/dm-core' +import { Typography } from '@equinor/eds-core-react' +import styled from 'styled-components' +import type { + TGridItem, + TGridItemStyle, + TGridPluginConfig, +} from '../../grid/types' +import { ensureModel, GRID_PLUGIN_NAME, isContainerItem } from '../model/model' + +/** + * Read-only site renderer. + * + * The shared grid plugin renders each widget through `ViewCreator`, which calls + * DMSS `attributeGet` to resolve the target attribute's type. That resolution + * walks the whole stored document and requires a `type` on every nested object — + * but the builder's widgets carry free-form inline `config` (e.g. a heading's + * `{ text, level, align }`) with no blueprint, so DMSS cannot resolve a saved + * site and every widget spins on `` forever. + * + * Self-contained widgets (scope `''`) don't bind to document data, so there is + * nothing to resolve: we render their plugin directly with the inline config, + * exactly like `InlineRecipeView` does after `ViewCreator` has resolved the + * target. Widgets that *do* bind data (non-empty scope) keep the original + * `ViewCreator` path so their behaviour is unchanged. + */ + +const HORIZONTAL_ALIGN: Record = { + left: 'flex-start', + center: 'center', + right: 'flex-end', +} + +const VERTICAL_ALIGN: Record = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', +} + +const styleToCss = (style?: TGridItemStyle): React.CSSProperties => + style + ? { + textAlign: style.textAlign, + alignItems: style.textAlign + ? HORIZONTAL_ALIGN[style.textAlign] + : undefined, + justifyContent: style.verticalAlign + ? VERTICAL_ALIGN[style.verticalAlign] + : undefined, + fontSize: style.fontSize, + fontWeight: style.bold ? 700 : undefined, + color: style.color, + padding: style.padding, + } + : {} + +const Grid = styled.div<{ $config: TGridPluginConfig }>` + display: grid; + width: 100%; + height: fit-content; + grid-template-rows: ${({ $config }) => + $config.size.rowSizes + ? $config.size.rowSizes.join(' ') + : `repeat(${$config.size.rows}, 1fr)`}; + grid-template-columns: ${({ $config }) => + $config.size.columnSizes + ? $config.size.columnSizes.join(' ') + : `repeat(${$config.size.columns}, 1fr)`}; + grid-column-gap: ${({ $config }) => $config.size.columnGap}; + grid-row-gap: ${({ $config }) => $config.size.rowGap}; +` + +const Cell = styled.div<{ $item: TGridItem; $config: TGridPluginConfig }>` + grid-area: ${({ $item }) => + `${$item.gridArea.rowStart} / ${$item.gridArea.columnStart} / ${ + $item.gridArea.rowEnd + 1 + } / ${$item.gridArea.columnEnd + 1}`}; + border: ${({ $config }) => + $config.showItemBorders + ? `${$config.itemBorder.size} ${$config.itemBorder.style} ${$config.itemBorder.color}` + : undefined}; + border-radius: ${({ $config }) => + $config.showItemBorders ? $config.itemBorder.radius : undefined}; + overflow: auto; +` + +const Content = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + min-height: 0; + width: 100%; +` + +type TWidgetProps = { + item: TGridItem + idReference: string + type: string + onSubmit?: (data: any) => void + onChange?: (data: any) => void +} + +const Widget = ({ + item, + idReference, + type, + onSubmit, + onChange, +}: TWidgetProps): React.ReactElement => { + const { getUiPlugin } = useApplication() + const { viewConfig } = item + const recipe = viewConfig.recipe + + // Data-bound widgets (non-empty scope) still need DMSS to resolve their + // target, so keep the original ViewCreator path for them. + if ( + viewConfig.scope || + typeof recipe !== 'object' || + recipe === null || + !recipe.plugin + ) { + return ( + + ) + } + + const UiPlugin = getUiPlugin(recipe.plugin) + return ( + + ) +} + +export type TSiteGridProps = { + config: TGridPluginConfig + idReference: string + type: string + onSubmit?: (data: any) => void + onChange?: (data: any) => void +} + +/** Render a grid layout read-only, rendering each widget's plugin directly. */ +export const SiteGrid = ({ + config, + idReference, + type, + onSubmit, + onChange, +}: TSiteGridProps): React.ReactElement => ( + + {config.items.map((item, index) => ( + + {item.title && ( + + {item.title} + + )} + + + {isContainerItem(item) && + item.viewConfig.recipe && + typeof item.viewConfig.recipe === 'object' && + item.viewConfig.recipe.plugin === GRID_PLUGIN_NAME ? ( + + ) : ( + + )} + + + + ))} + +) diff --git a/packages/dm-core-plugins/src/builder/components/SiteShellView.tsx b/packages/dm-core-plugins/src/builder/components/SiteShellView.tsx new file mode 100644 index 000000000..2c15d1312 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/SiteShellView.tsx @@ -0,0 +1,48 @@ +import { ErrorBoundary } from '@development-framework/dm-core' +import * as Styled from '../styles' + +type SiteShellViewProps = { + navbar: React.ReactNode + navSidebar: React.ReactNode + /** Message shown if a widget fails to render. */ + errorMessage: string + /** When set, the page content is constrained to this width (device preview). */ + maxWidth?: string + children: React.ReactNode +} + +/** + * Chrome-free rendering of the built site: the top navbar, the page nav sidebar + * and the page content. Shared by the read-only viewer and the editor's preview + * mode (which additionally constrains the width to emulate a device). + */ +export const SiteShellView = ({ + navbar, + navSidebar, + errorMessage, + maxWidth, + children, +}: SiteShellViewProps): React.ReactElement => { + const content = ( + {children} + ) + return ( + + + {navbar} + + {navSidebar} + + {maxWidth ? ( + + {content} + + ) : ( + content + )} + + + + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/TableSourceField.styles.ts b/packages/dm-core-plugins/src/builder/components/TableSourceField.styles.ts new file mode 100644 index 000000000..21d441586 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/TableSourceField.styles.ts @@ -0,0 +1,15 @@ +import styled from 'styled-components' +import * as Styled from '../styles' + +export const HiddenInput = styled.input` + display: none; +` + +export const MonospaceTextarea = styled(Styled.Textarea)` + font-family: monospace; + min-height: 140px; +` + +export const ErrorHelp = styled(Styled.FieldHelp)` + color: #c00; +` diff --git a/packages/dm-core-plugins/src/builder/components/TableSourceField.tsx b/packages/dm-core-plugins/src/builder/components/TableSourceField.tsx new file mode 100644 index 000000000..ccb46a01d --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/TableSourceField.tsx @@ -0,0 +1,151 @@ +import { Button, Progress } from '@equinor/eds-core-react' +import { type ChangeEvent, useRef, useState } from 'react' +import * as Styled from '../styles' +import { markdownToRows, rowsToMarkdown } from '../utils/markdownTable' +import { MAX_COLS, MAX_ROWS, parseSpreadsheet } from '../utils/spreadsheet' +import * as S from './TableSourceField.styles' + +type TTableSourceFieldProps = { + label: string + /** Current table cells (first row = header). */ + value: string[][] + onChange: (rows: string[][] | undefined) => void +} + +type TMode = 'upload' | 'write' + +/** + * Lets the author fill the table widget two ways: + * + * - **Import file**: pick a CSV/TSV/Excel file, parsed locally into cells. + * - **Write table**: type a GitHub-style markdown table by hand. The draft is + * seeded from the current cells, so an imported table can be tweaked inline. + * + * Both paths produce the same `string[][]` the table widget renders, so no + * document or scope binding is needed. + */ +export const TableSourceField = ({ + label, + value, + onChange, +}: TTableSourceFieldProps): React.ReactElement => { + const input = useRef(null) + const [mode, setMode] = useState('upload') + const [draft, setDraft] = useState(() => rowsToMarkdown(value)) + const [loading, setLoading] = useState(false) + const [error, setError] = useState() + const [notice, setNotice] = useState() + + const hasContent = Array.isArray(value) && value.length > 0 + + // Seed the markdown draft from the current cells whenever the author enters + // write mode so an uploaded table is shown ready to edit. + const enterWrite = () => { + setDraft(rowsToMarkdown(value)) + setError(undefined) + setNotice(undefined) + setMode('write') + } + + const handleDraft = (text: string) => { + setDraft(text) + const rows = markdownToRows(text) + onChange(rows.length > 0 ? rows : undefined) + } + + const handleFile = async (event: ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) return + setError(undefined) + setNotice(undefined) + setLoading(true) + try { + const { rows, totalRows, totalCols, truncated } = + await parseSpreadsheet(file) + if (rows.length === 0) { + setError('That file had no rows') + return + } + onChange(rows) + setDraft(rowsToMarkdown(rows)) + if (truncated) { + setNotice( + `Large file: kept the first ${Math.min(totalRows, MAX_ROWS)} of ${totalRows} rows` + + (totalCols > MAX_COLS + ? ` and ${MAX_COLS} of ${totalCols} columns` + : '') + + '.' + ) + } + } catch { + setError('Could not read that file') + } finally { + setLoading(false) + if (input.current) input.current.value = '' + } + } + + return ( + <> + {label} + + setMode('upload')} + > + Import file + + + Write table + + + + {mode === 'upload' ? ( + <> + + input.current?.click()} + > + {loading ? ( + + ) : hasContent ? ( + 'Replace data…' + ) : ( + 'Upload CSV / Excel…' + )} + + > + ) : ( + <> + handleDraft(event.target.value)} + /> + + First row is the header. Separate columns with |; the{' '} + --- line is optional. + + > + )} + + {notice && {notice}} + {error && {error}} + > + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/TableUploadField.tsx b/packages/dm-core-plugins/src/builder/components/TableUploadField.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/dm-core-plugins/src/builder/components/TemplatesMenu.tsx b/packages/dm-core-plugins/src/builder/components/TemplatesMenu.tsx new file mode 100644 index 000000000..ee0359736 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/TemplatesMenu.tsx @@ -0,0 +1,46 @@ +import { Button, Menu } from '@equinor/eds-core-react' +import { useRef, useState } from 'react' +import { TEMPLATES, type TTemplate } from '../model/templates' + +export const TemplatesMenu = ({ + onApply, +}: { + onApply: (template: TTemplate) => void +}): React.ReactElement => { + const anchorRef = useRef(null) + const [open, setOpen] = useState(false) + + return ( + <> + setOpen((value) => !value)} + > + Templates + + setOpen(false)} + placement='bottom-start' + > + + {TEMPLATES.map((template) => ( + { + onApply(template) + setOpen(false) + }} + > + {template.label} + + ))} + + + > + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/Toast.tsx b/packages/dm-core-plugins/src/builder/components/Toast.tsx new file mode 100644 index 000000000..dd19eb54a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/Toast.tsx @@ -0,0 +1,27 @@ +import { Snackbar } from '@equinor/eds-core-react' +import type { TToast } from '../utils/toast' + +/** + * Renders the current builder toast. Keyed by toast id so a new message + * restarts the Snackbar's auto-hide timer. + */ +export const Toast = ({ + toast, + onClose, +}: { + toast: TToast | null + onClose: () => void +}): React.ReactElement | null => { + if (!toast) return null + return ( + + {toast.message} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/components/WidgetPalette.styles.ts b/packages/dm-core-plugins/src/builder/components/WidgetPalette.styles.ts new file mode 100644 index 000000000..f5b9c0bc6 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/WidgetPalette.styles.ts @@ -0,0 +1,6 @@ +import styled from 'styled-components' +import * as Styled from '../styles' + +export const PaletteCard = styled(Styled.PaletteCard)<{ $dragging: boolean }>` + opacity: ${(props) => (props.$dragging ? 0.4 : 1)}; +` diff --git a/packages/dm-core-plugins/src/builder/components/WidgetPalette.tsx b/packages/dm-core-plugins/src/builder/components/WidgetPalette.tsx new file mode 100644 index 000000000..15754c25a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/components/WidgetPalette.tsx @@ -0,0 +1,70 @@ +import { useDraggable } from '@dnd-kit/core' +import { Icon, Typography } from '@equinor/eds-core-react' +import { BLOCKS } from '../model/blocks' +import * as Styled from '../styles' +import type { TBlock, TBlockCategory } from '../types' +import { ICONS } from '../utils/icons' +import * as S from './WidgetPalette.styles' + +const CATEGORY_LABELS: Record = { + layout: 'Layout', + content: 'Content', + media: 'Media', + data: 'Data', +} + +const PaletteCard = ({ + block, + onAdd, +}: { + block: TBlock + onAdd: (block: TBlock) => void +}) => { + const { attributes, listeners, setNodeRef, isDragging } = useDraggable({ + id: `palette:${block.id}`, + data: { blockId: block.id }, + }) + + return ( + onAdd(block)} + {...listeners} + {...attributes} + > + + {block.label} + + ) +} + +export const WidgetPalette = ({ + onAdd, +}: { + onAdd: (block: TBlock) => void +}): React.ReactElement => { + const categories = Array.from( + new Set(BLOCKS.map((block) => block.category)) + ) as TBlockCategory[] + + return ( + + Widgets + {categories.map((category) => ( + + + {CATEGORY_LABELS[category]} + + {BLOCKS.filter((block) => block.category === category).map( + (block) => ( + + ) + )} + + ))} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/docs/ADDING_WIDGETS.md b/packages/dm-core-plugins/src/builder/docs/ADDING_WIDGETS.md new file mode 100644 index 000000000..76516a71a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/docs/ADDING_WIDGETS.md @@ -0,0 +1,331 @@ +# Adding a widget to the UI builder + +This guide shows how to add a new widget (a "block" in the palette) to the +drag-and-drop website builder — for example a **date picker**, a **rating**, a +**countdown**, or anything else. + +You do **not** need to touch the builder's internals. Every widget lives in its +own small file, and you register it by adding **one line** to a list. That's it. + +> **TL;DR** — Create one file in `builder/widgets/`, add one import line to +> `builder/widgets/index.ts`. Done. Skip to [The 3-minute version](#the-3-minute-version). + +--- + +## How the builder is organised + +Everything about one widget lives in a single file: + +``` +packages/dm-core-plugins/src/builder/widgets/ +├── index.ts ← the ONE list of all widgets (you add a line here) +├── types.ts ← the shape of a widget definition +├── heading.widget.ts ← one file per widget … +├── button.widget.ts +├── table.widget.ts +├── chart.widget.ts +├── metric.widget.ts +├── datePicker.widget.tsx ← 👀 a complete worked example — copy this +└── … +``` + +Each `*.widget.ts` file exports a **widget definition** with three things: + +| Part | What it is | +| ------- | -------------------------------------------------------------------------- | +| `block` | How the widget looks & behaves in the palette and the properties panel. | +| `icon` | The little icon on the palette card. | +| `load` | *(optional)* The component that draws the widget on the finished page. | + +When you add your file to the list in `index.ts`, the builder automatically: + +- shows a **draggable card** in the palette, +- shows your **settings fields** in the properties panel on the right, +- **renders** your widget in Preview and on the published page. + +No other files to edit. (Behind the scenes `model/blocks.ts`, `utils/icons.ts` and the +plugin registry all read from this one list.) + +--- + +## Two kinds of widgets + +**1. Self-contained widget (has its own `load`).** +It draws itself from its settings — no database needed. Headings, buttons, +tables, charts, metrics and the date-picker example all work this way. Choose +this for almost everything. + +**2. Reuse-an-existing-plugin widget (no `load`).** +It's just a palette card that points at a plugin that already exists in the +package (for example a data/entity plugin). Use this only when you're wrapping +an existing plugin — the card carries a `recipe` (the plugin key) and an +optional `defaultConfig`, but no `load`. + +The rest of this guide covers kind #1, which is what you'll want almost always. + +--- + +## The 3-minute version + +Say you want a **Date picker**. Here's the whole thing. + +### Step 1 — Create `builder/widgets/datePicker.widget.tsx` + +```tsx +import type { IUIPlugin } from '@development-framework/dm-core' +import { calendar } from '@equinor/eds-icons' +import { useState } from 'react' +import type { TWidgetDefinition } from './types' + +// What settings this widget stores. +interface DatePickerConfig { + label?: string + value?: string // starting date, YYYY-MM-DD + helperText?: string +} + +// The component drawn on the finished page. +const DatePickerWidget = ( + props: Omit & { config: DatePickerConfig } +): React.ReactElement => { + const { label = 'Pick a date', value = '', helperText } = props.config + const [date, setDate] = useState(value) + + return ( + + + {label} + setDate(e.target.value)} /> + + {helperText ? {helperText} : null} + + ) +} + +// The palette entry. +export const datePickerWidget: TWidgetDefinition = { + icon: calendar, + load: () => Promise.resolve({ default: DatePickerWidget }), + block: { + id: 'date-picker', + label: 'Date picker', + icon: 'calendar', + category: 'data', + description: 'Let visitors pick a date.', + contentModel: 'content', + defaultSize: { columns: 4, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-date-picker', + defaultConfig: { label: 'Pick a date' }, + hideTitle: true, + fields: [ + { label: 'Label', type: 'text', target: { kind: 'config', key: 'label' } }, + { label: 'Default date', type: 'text', target: { kind: 'config', key: 'value' } }, + { label: 'Helper text', type: 'text', target: { kind: 'config', key: 'helperText' } }, + ], + }, +} +``` + +> A full, commented version of this exact file already lives at +> [`widgets/datePicker.widget.tsx`](../widgets/datePicker.widget.tsx) — open it +> and copy it. + +### Step 2 — Register it (the one line) + +Open [`builder/widgets/index.ts`](../widgets/index.ts) and add two lines: + +```ts +import { datePickerWidget } from './datePicker.widget' // ← 1) import it + +export const WIDGET_DEFINITIONS: TWidgetDefinition[] = [ + // …all the existing widgets… + datePickerWidget, // ← 2) add it to the list +] +``` + +### Step 3 — See it + +Start the example app, open the builder, and your **Date picker** card is in the +palette under **Data**. Drag it onto the page. That's it. 🎉 + +--- + +## Filling in the `block` — every option explained + +```ts +block: { + id: 'date-picker', // unique short id (kebab-case) + label: 'Date picker', // name shown on the palette card + icon: 'calendar', // MUST match the `icon:` above (see Icons below) + category: 'data', // which palette group: layout | content | media | data + description: 'Let visitors pick a date.', // tooltip + edit-mode placeholder + contentModel: 'content', // 'content' = draws itself; 'data' = binds to an entity + defaultSize: { columns: 4, rows: 1 }, // size (in grid cells) when first dropped + recipe: '@development-framework/dm-core-plugins/static-date-picker', // unique id, see below + defaultConfig: { label: 'Pick a date' }, // starting settings so it looks right immediately + hideTitle: true, // true for self-drawing widgets (no auto title bar) + fields: [ /* settings shown in the properties panel — see next section */ ], +}, +``` + +A few rules of thumb: + +- **`id`** and **`recipe`** must be **unique** across all widgets. A test will + fail loudly if they aren't (see [Troubleshooting](#troubleshooting)). +- **`recipe`** is the internal address of your component. For self-contained + widgets, use the pattern + `@development-framework/dm-core-plugins/static-`. +- **`category`** picks the palette section. Pick the closest of: `layout`, + `content`, `media`, `data`. +- **`hideTitle: true`** for widgets that draw their own content (almost all of + them). Leave it off for data-bound widgets that want a title bar. + +--- + +## The `fields` — building the properties panel + +Every entry in `fields` becomes one control in the right-hand properties panel. +Whatever the user types is saved into the widget's config and passed to your +component as `props.config`. + +```ts +{ + label: 'Label', // shown next to the control + type: 'text', // which control to render (table below) + target: { kind: 'config', key: 'label' }, // where to store the value + placeholder: '2026-07-01', // optional greyed-out hint + help: 'Caption shown above the picker.', // optional helper line + options: [ … ], // only for type: 'select' +} +``` + +### Available field `type`s + +| `type` | Control shown | Good for | +| --------------- | ------------------------ | ------------------------------------------- | +| `text` | single-line text box | labels, URLs, colors, short values | +| `textarea` | multi-line text box | paragraphs, lists of numbers | +| `number` | number input | sizes, counts, decimals | +| `boolean` | on/off switch | toggles like "Open in new tab" | +| `select` | dropdown (needs `options`) | a fixed set of choices | +| `image-upload` | file picker for images | image widgets | +| `table-source` | "write a table or import a file" editor | tables & chart data | + +A `select` field needs `options`: + +```ts +{ + label: 'Alignment', + type: 'select', + target: { kind: 'config', key: 'align' }, + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], +} +``` + +### Where a field stores its value — `target` + +| `target` | Saves to… | Read in your component as… | +| ----------------------------------- | ---------------------------------- | -------------------------- | +| `{ kind: 'config', key: 'xyz' }` | your widget's settings (`config.xyz`) | `props.config.xyz` | +| `{ kind: 'label' }` | the widget's display label | (framework-handled) | +| `{ kind: 'scope' }` | the data binding (a DMSS entity) | (framework-handled) | + +For self-contained widgets you'll almost always use `{ kind: 'config', key: … }`. + +--- + +## Writing the component + +Your component receives its settings on `props.config`. Always give every +setting a default so a freshly dropped widget looks right: + +```tsx +const { label = 'Pick a date', value = '' } = props.config +``` + +Tips: + +- Add the `dm-plugin-padding` class to your outer element for consistent spacing. +- Use `style={{ width: '100%' }}` so the widget fills its grid cell. +- If content might overflow a small cell, add `overflow: 'hidden'`. +- Keep it **dependency-free** where you can (plain HTML/SVG), the same way the + built-in Chart and Metric widgets do. That keeps the app small and fast. + +--- + +## Icons + +Each widget imports its own icon from `@equinor/eds-icons` and sets it in two +places that must match: + +```ts +import { calendar } from '@equinor/eds-icons' +// … +icon: calendar, // the actual icon data +block: { icon: 'calendar' /* the same name as a string */ } +``` + +To find a valid icon name, browse the [EDS icons gallery](https://eds.equinor.com/0eb62b4a1/p/9028cb-icons/b/393a97), +or list them locally: + +```bash +node -e "console.log(Object.keys(require('@equinor/eds-icons')).join('\n'))" | grep calendar +``` + +You do **not** need to edit `icons.ts` — widget icons are picked up +automatically. + +--- + +## Verifying your widget + +From the repo root: + +```bash +# Type-check +yarn workspace @development-framework/dm-core-plugins exec tsc --noEmit + +# Run the builder tests (includes the registry guardrails) +yarn workspace @development-framework/dm-core-plugins exec jest src/builder + +# Format & lint your new file +npx biome check --write packages/dm-core-plugins/src/builder/widgets/ +``` + +Then try it live in the example app (see the builder [README](./README.md)). + +--- + +## Troubleshooting + +The registry test (`tests/registry.test.ts`) checks common mistakes for you. +If a test fails, here's what it means: + +| Message | Fix | +| --------------------------------------------------- | ------------------------------------------------------------------- | +| *gives every widget a unique block id* | Two widgets share an `id`. Rename one. | +| *gives every widget a unique recipe* | Two widgets share a `recipe`. Rename one. | +| *resolves every block icon to icon data* | `block.icon` (string) doesn't match the imported `icon`. Make them the same. | +| *registers a runtime component for every static widget* | You gave a `load` but no matching component, or vice-versa. | + +Other common issues: + +- **My card doesn't appear.** Did you add it to the array in `index.ts`? +- **It shows a grey placeholder in the canvas.** That's normal — the real + widget only renders in **Preview** and on the published page. Edit mode shows + a placeholder with the widget's `description`. +- **My settings don't do anything.** Check the `target` `key` matches the + property you read in the component (`props.config.`). + +--- + +## Cheat sheet + +1. Copy `datePicker.widget.tsx` → `myThing.widget.tsx`. +2. Change the component, the `id`, `recipe`, `label`, `icon`, and `fields`. +3. Import it and add it to the list in `index.ts`. +4. `tsc`, `jest`, `biome` → done. diff --git a/packages/dm-core-plugins/src/builder/docs/README.md b/packages/dm-core-plugins/src/builder/docs/README.md new file mode 100644 index 000000000..71b4d000b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/docs/README.md @@ -0,0 +1,308 @@ +# Website builder + +A drag-and-drop, WordPress-style page builder for the Data Modeling framework. + +It lets users assemble pages visually — dragging widgets onto a canvas — instead +of hand-authoring nested `UiRecipe` / `GridPluginConfig` JSON. + +> **New here?** The **[user guide: Build & publish a website](./USER_GUIDE.md)** +> is a no-code, step-by-step walkthrough for people using the builder. This +> README is the technical/developer reference. + +## Design principle: build format == runtime format + +The builder's editor model is the **same shape the runtime [`grid`](../../grid) +plugin already consumes** (`TGridPluginConfig`). Anything authored in the builder +serializes to the canonical grid entity JSON and renders with the existing grid +renderer — there is no separate runtime format to maintain. + +A "widget" dropped on the canvas is a grid item whose `viewConfig` points at a +plugin/recipe (Text, Image, Table, or a nested Section). + +## Status: Phase 5 (publish & docs) + +Implemented: + +- Plugin registered as `@development-framework/dm-core-plugins/builder`. +- Widget palette (Section, Text, Image, Table, Chart, Metric…) with draggable + cards (`@dnd-kit`). +- CSS-grid canvas: drag a block from the palette (or click it) to add a widget; + select, delete and **duplicate** widgets; drop-target highlight and empty state. +- **Drag widgets by their header to reposition** them (pixel deltas snapped to + grid cells) and **resize via a corner handle**. Moves and resizes are clamped + to the grid and rejected when they would overlap another widget. +- **Property inspector** (right sidebar) for the selected widget — edit through + typed form controls instead of JSON: + - General: title and label. + - Layout: column/row position and width/height (clamped, overlap-guarded). + - Data binding: the `scope` attribute path the widget renders. + - Settings: block-specific fields from the manifest (e.g. image caption/size), + targeting config keys the runtime plugin actually consumes. +- Blocks carry a `defaultConfig` so a dropped widget renders in preview without + manual setup. +- Edit / Preview toggle. Preview round-trips the model through + `serialize`/`deserialize` and renders it with the runtime `grid` plugin. + Existing pages can be loaded for editing via `config.initialConfig`. +- Read-only "Advanced: JSON" inspector. +- **Section nesting**: a Section widget is a nested grid. Drill into it (its + "Open" button or a double-click) to edit its children on the same canvas; a + breadcrumb shows the path and navigates back out. +- **Starter templates**: apply a preset layout (Blank, Landing, Dashboard, + Article) from the toolbar to seed the page. +- **Device-width preview**: switch the canvas/preview width between desktop, + tablet and mobile frames. +- **Undo/redo** (toolbar + `Ctrl/Cmd+Z`, `Shift+Z`, `Y`) over an immutable + history; consecutive related edits (a drag-resize, typing in one field) + collapse into a single undo step. +- **Keyboard editing**: `Ctrl/Cmd+C/V` copy & paste, `Ctrl/Cmd+D` duplicate, + `Delete`/`Backspace` remove, `Escape` deselect (inert while typing in a field). +- **Outline panel** beneath the palette: select any widget in the current grid + and drill into sections. +- **Toast feedback**: overlap-rejected moves/resizes and edit actions surface a + friendly message instead of failing silently. +- **Dirty-state & autosave**: a toolbar status (All changes saved / Saving… / + Unsaved changes) reflects unsaved work; edits autosave through `onChange` (when + provided) after a short debounce, and the browser warns before leaving with + unsaved changes. +- Pure, immutable editor model with unit tests (`model.ts`, `gridMetrics.ts`, + `templates.ts`, `history.ts`). +- **Recursive (de)serialization**: Section grids are serialized with the same + DMSS `type` discriminators as the root, so nested layouts render at runtime + and round-trip cleanly through `serialize`/`deserialize`. + +## Try it in the example app + +A demo page is wired into the example DemoDataSource. After `./reset-app.sh` +and `yarn start:example`, open: + +``` +/view/?documentId=dmss://DemoDataSource/$builderPageExample +``` + +It mounts the builder with a seeded layout (a page heading + a nested Section) via +`config.initialConfig`. The blueprint/recipe/entity live under +`example/app/data/DemoDataSource/{plugins,recipes}/builder/example/`. + +## Read-only viewer + +Set `config.readOnly: true` to mount a built site as a **read-only viewer**: the +published navbar, page navigation and content render, but the editing toolbar, +palette and inspector are all hidden and nothing can mutate or persist the site. +Use this to show a finished site to end users (who should browse but not edit) +while the same entity stays editable from a builder recipe with `readOnly` +unset. Multi-page navigation keeps working in the viewer. + +To gate editing by role instead of per-recipe, set `config.editorRoles` to the +list of role names allowed to edit. Users whose current role is not listed get +the same read-only viewer; an empty or absent list leaves editing open to +everyone. + +## Embedding & hosting (internal) + +The builder is a **DMSS plugin**, not a standalone widget: it reads and writes a +document through DMSS at runtime. "Hosting" a built site internally therefore +means mounting the plugin inside a DMSS-connected React app — you do **not** need +a separate web server or database for the builder itself. + +### 1. Storage — you already have it + +On save the plugin never talks to a database directly; it calls +`updateDocument(idReference)` and **DMSS owns storage**. The persisted site is a +plain JSON document validated by the `Site` / `Page` / `Navbar` blueprints, so +the storage backend is chosen per DMSS **data source**, not in the builder: + +- **MongoDB** (typical default), **Azure Blob Storage**, or local disk. +- Want **Cosmos DB**? Point the DMSS Mongo backend at a Cosmos account using its + **Mongo API** — no builder or plugin code changes, and you keep DMSS + versioning, access control and blueprint validation. A *native* Cosmos backend + would be work in the DMSS repo and is only worth it for a hard Cosmos-native + requirement. + +Because a site is just JSON, backups/migrations are ordinary document +export/import. `serializeSite` / `deserializeSite` (exported from the package +root) are the canonical shape if you ever read or transform it yourself. + +### 2. Mount the editor + +Every consumer app already wraps the tree in `DMApplicationProvider` (auth, +application, `dmssBasePath`) and passes the plugin map. Two equivalent ways to +render the builder for an entity: + +**Recipe-driven (recommended — how the example app works).** Attach a `builder` +recipe to the entity's blueprint and let `EntityView` resolve it: + +```tsx +import { DMApplicationProvider, EntityView } from '@development-framework/dm-core' +import plugins from './plugins' // { ...dmCorePlugins } + + + + +``` + +**Direct mount.** When you want the builder on a fixed entity without a recipe +lookup, render the component yourself (still inside the provider): + +```tsx +import { BuilderPlugin } from '@development-framework/dm-core-plugins/builder' + + +``` + +### 3. Serve the built site to viewers + +The *same* entity is the published site — there is no separate deploy step for +internal hosting. Give end users the **read-only viewer** by mounting the plugin +with `config.readOnly: true`, or gate by role with `config.editorRoles` so +editors get the editor and everyone else gets the viewer against the very same +document: + +```tsx +// A "published site" route for internal users: + +``` + +The viewer renders the navbar, multi-page navigation and content only — no +toolbar, palette or inspector, and nothing can mutate or persist the site. A +common setup is **two recipes on one entity**: an authoring recipe (editing open +or `editorRoles`-gated) and a viewer recipe with `readOnly: true`, wired to +different routes. + +> **Scope:** this is internal hosting — every viewer is an authenticated DMSS +> user, and the site is rendered live from DMSS. Public, no-backend hosting would +> need a standalone `value`-driven viewer (no `useDocument`) plus a static export +> of the site JSON; that is out of scope for v1. + +### 4. Finding sites — the Site directory + +Linking to one site by its id is fine, but users also need a **home page that +lists every site** so they can browse what's been built. That's the **Site +directory** plugin (`@development-framework/dm-core-plugins/builder/directory`). + +It searches DMSS for every entity of the Site type and shows them as cards that +open the viewer, plus an optional **New site** button that creates a fresh Site +entity and jumps into the editor. It has no hard router dependency — cards are +plain links built from a configurable URL template (defaulting to the example +app's `/view?documentId=` convention). + +```tsx +import { SiteDirectoryPlugin } from '@development-framework/dm-core-plugins/builder' + +// Or, more commonly, mount it via a recipe using the plugin name +// '@development-framework/dm-core-plugins/builder/directory'. + +``` + +Config (`TSiteDirectoryConfig`, blueprint `SiteDirectoryConfig`): + +| Field | Default | Purpose | +| --- | --- | --- | +| `heading` | `"Websites"` | Title above the gallery. | +| `entityType` | `SITE_TYPE` | Which entity type to list. | +| `dataSources` | app's visible data sources | Where to search. | +| `targetDataSource` | first searched data source | Where **New site** creates the entity; absent ⇒ button hidden. | +| `viewUrlTemplate` | `/view?documentId=dmss://{dataSource}/{id}` | Link built per site (`{id}`, `{dataSource}` substituted). | +| `newSiteName` | `"New site"` | Name for a created site. | +| `allowCreate` | `true` | Set `false` for a read-only gallery. | + +**How "New site" works:** it serializes `createEmptySite()` (a navbar + one blank +page), `documentAdd`s it under `targetDataSource` with a `name`, then navigates +to the new entity via `viewUrlTemplate`. Point that route at an authoring recipe +so the new site opens in the editor. + +## Architecture + +The plugin is organised into folders by responsibility: + +``` +builder/ + BuilderPlugin.tsx # main editor entry (state, DnD, mode switch) + SiteDirectoryPlugin.tsx # standalone site gallery entry + index.ts # public barrel + docs/ README.md, USER_GUIDE.md, ADDING_WIDGETS.md + types/ index.ts (editor model, config, block & field types), siteDirectory.ts + model/ site.ts, model.ts, templates.ts, blocks.ts, history.ts + utils/ gridMetrics.ts, markdownTable.ts, mathUtils.ts, spreadsheet.ts, toast.ts, icons.ts + styles/ index.ts (shared styled-components), directory.styles.ts + static/ StaticChartPlugin.tsx, StaticTablePlugin.tsx, staticWidgets.tsx + hooks/ usePersistence.ts, useEditorShortcuts.ts + components/ UI pieces + co-located *.styles.ts + widgets/ the widget registry (one file per widget) + tests/ all builder unit tests +``` + +| File | Responsibility | +| --- | --- | +| `types/index.ts` | Editor model, plugin config, block and inspector-field types. | +| `widgets/` | **The widget registry** — one file per palette widget (metadata + icon + optional component loader), collected in `widgets/index.ts`. Adding a widget happens here and nowhere else. See [`ADDING_WIDGETS.md`](./ADDING_WIDGETS.md). | +| `model/blocks.ts` | Thin re-export of `BLOCKS` / `getBlock` from the widget registry. | +| `utils/icons.ts` | Editor-chrome icons, merged with each widget's own icon from the registry. | +| `model/model.ts` | Pure transforms (add/remove/move/resize/duplicate/insert/setters), nesting helpers and serialize. | +| `model/site.ts` | Site (multi-page) serialize/deserialize and page helpers. | +| `model/history.ts` | Immutable undo/redo stack + `useHistory` hook (with coalescing). | +| `model/templates.ts` | Starter page presets that build ready-to-edit models. | +| `utils/gridMetrics.ts` | Converts drag/resize pixel deltas into grid-cell deltas. | +| `utils/toast.ts` | `useToast` single-slot transient feedback state. | +| `hooks/usePersistence.ts` | Hydration, autosave, manual save and unload guard. | +| `hooks/useEditorShortcuts.ts` | Keyboard shortcut bindings for the editor. | +| `styles/index.ts` | Shared styled-components for the editor chrome. | +| `BuilderPlugin.tsx` | Plugin entry: state, history, `DndContext`, mode switch. | +| `components/BuilderToolbar.tsx` | Presentational editor toolbar. | +| `components/SiteShellView.tsx` | Shared shell for the locked viewer and preview mode. | +| `SiteDirectoryPlugin.tsx` | Standalone plugin: a gallery of saved sites (search by type) + a "New site" action. Config in `types/siteDirectory.ts`. | +| `components/WidgetPalette.tsx` | Draggable palette cards grouped by category. | +| `components/Outline.tsx` | List of widgets in the active grid for select/drill-in. | +| `components/Canvas.tsx` | Grid canvas: drag-to-move, resize, select, drill-in, device frame. | +| `components/Inspector.tsx` | Property panel for the selected widget. | +| `components/TemplatesMenu.tsx` | Toolbar menu listing the starter templates. | +| `components/Toast.tsx` | Renders the current toast (EDS `Snackbar`). | +| `tests/` | All builder unit tests (model, grid metrics, history, templates, site, markdown/math helpers, widget registry). | + + +## Adding your own widgets + +The palette is fully extensible. Adding a widget (a date picker, a rating, a +countdown…) is a **one-file, one-line** change — you don't touch the builder's +internals. See the step-by-step **[Adding a widget guide](./ADDING_WIDGETS.md)**, +which walks through a complete date-picker example. A working reference lives at +[`widgets/datePicker.widget.tsx`](../widgets/datePicker.widget.tsx). + +## Content model + +- **Content widgets** (Text, Image, Table, Chart, Metric, Date picker…) render + self-contained content: Text and Table store inline markdown, Image stores an + uploaded file address. Image upload and Table CSV/Excel upload are handled in + the inspector; binding a `viewConfig.scope` to a document still works as an + alternative. +- **Layout** (Section) is a container (a nested grid). + +## Known limitations + +- Widgets are keyed by array index for React keys and dnd ids. This is safe for + the current append/delete/duplicate/paste operations; a stable per-widget id + should be introduced before adding free reordering (tracked for Phase 5). +- Grid gaps are assumed to be `px` values when snapping drag/resize deltas. +- Inspector config fields are silent no-ops for widgets that use a recipe + reference (string) rather than an inline recipe. + +## Roadmap + +- Phase 1 — move/resize widgets on the canvas; load existing pages. ✅ +- Phase 2 — inspector that edits each widget via typed controls (no JSON). ✅ +- Phase 3 — sections/nesting, responsive breakpoints, templates. ✅ +- Phase 4 — undo/redo, copy/paste, outline, toasts, autosave & dirty guard. ✅ +- Phase 5 — publish flow, example-app entry and demo blueprints. ✅ diff --git a/packages/dm-core-plugins/src/builder/docs/USER_GUIDE.md b/packages/dm-core-plugins/src/builder/docs/USER_GUIDE.md new file mode 100644 index 000000000..fb84204cd --- /dev/null +++ b/packages/dm-core-plugins/src/builder/docs/USER_GUIDE.md @@ -0,0 +1,245 @@ +# Build & publish a website — user guide + +A friendly, step-by-step guide to building a page or a multi-page site with the +drag-and-drop **Website builder** — no code, no JSON. + +> **Who this is for:** anyone using the builder in the app. You do not need to be +> a developer. If you *are* a developer and want to add a new kind of widget, see +> the [Adding a widget guide](./ADDING_WIDGETS.md) instead. + +> 📸 *Screenshots referenced below (e.g. `[screenshot: the editor]`) are +> placeholders — capture them from your running app and drop them in.* + +--- + +## 1. Open the builder + +Open the entity that holds your site in the app. It opens in the **Website +builder** with the editing toolbar across the top. + +`[screenshot: the editor with the toolbar, palette, canvas and properties panel]` + +If you only see the finished page with **no** toolbar or palette, you're looking +at the **read-only viewer** — either the page was set to view-only, or your role +isn't allowed to edit it. See [§13 Who can see it](#13-publishing--who-can-see-it). + +--- + +## 2. The screen at a glance + +| Area | Where | What it does | +| --- | --- | --- | +| **Toolbar** | top | Templates, Undo/Redo, Save, device width, grid density, **Advanced: JSON**, **Edit** / **Preview**. | +| **Pages** | far left | Your list of pages (and sub-pages). Add, rename, delete, reorder. | +| **Widgets** | left | The palette of blocks you drag onto the page. | +| **Canvas** | middle | The page itself — a grid you arrange widgets on. | +| **Outline** | left, below Widgets | A list of the widgets on the current page; click to select or drill into a Section. | +| **Properties** | right | Settings for the currently selected widget. | + +--- + +## 3. Add your first widget + +Two ways — whichever you prefer: + +- **Drag** a card from **Widgets** onto the canvas, or +- **Click** a card to drop it onto the page. + +`[screenshot: dragging a Heading card onto the canvas]` + +In **Edit** mode a widget shows a **grey placeholder** with its name — that's +normal. You see the real thing in **Preview** (see [§11](#11-preview-device-width--grid-density)). + +--- + +## 4. Move & resize + +- **Move:** drag a widget by its **header** to a new spot. It snaps to the grid. +- **Resize:** drag the **corner handle**. +- Overlapping isn't allowed — a move or resize that would cover another widget is + rejected and you'll get a small message. + +Select a widget by clicking it. A selected widget shows **Duplicate** and +**Delete** buttons, plus the resize handle. + +--- + +## 5. Edit a widget's settings — the Properties panel + +Select a widget and the **Properties** panel on the right shows its settings, +grouped into sections: + +| Section | What you set | +| --- | --- | +| **General** | The widget's title and label. | +| **Layout** | Position (column/row) and size (width/height), in grid cells. Alignment where relevant. | +| **Data** | *(optional)* Bind the widget to a document field (a "scope") instead of typing content by hand. | +| **Settings** | The widget-specific options — e.g. a heading's text & level, an image's caption, a table's contents. | + +`[screenshot: the Properties panel for a selected Heading]` + +You type into normal form controls — never JSON. (There's a read-only +**Advanced: JSON** view in the toolbar if you're curious what's stored.) + +--- + +## 6. The widget catalog + +Everything currently in the palette, by category: + +### Layout +| Widget | What it's for | +| --- | --- | +| **Section** | A container to group and arrange other widgets. Double-click (or **Open section**) to edit what's inside; a breadcrumb takes you back out. | + +### Content +| Widget | What it's for | +| --- | --- | +| **Heading** | A page title or section heading (levels H1–H6, alignment, color). | +| **Text** | Markdown text — type it inline or bind it to a document. | +| **Button** | A call-to-action button that links somewhere. | +| **Divider** | A horizontal line to separate sections. | +| **Spacer** | Empty vertical space between widgets. | + +### Media +| Widget | What it's for | +| --- | --- | +| **Image** | An image or media asset — upload from your computer or bind to a file. | +| **Embed** | Embed a YouTube/Vimeo video or any external page. | + +### Data +| Widget | What it's for | +| --- | --- | +| **Table** | A table you fill in by hand or import from CSV/Excel. | +| **Chart** | A line or bar chart drawn from a small table of numbers. | +| **Metric** | A single big-number KPI computed from a list of values. | +| **Date picker** | Let visitors pick a date. | + +> Missing something (a rating, a countdown, a map…)? A developer can add it in a +> single file — see [ADDING_WIDGETS.md](./ADDING_WIDGETS.md). + +--- + +## 7. Build a multi-page site + +Use the **Pages** sidebar on the far left: + +- **Add page** — the button at the bottom adds a new top-level page. +- **Add sub-page** — the "+" on a page nests a page beneath it (for grouped + navigation). +- **Rename** — the pencil, or double-click the page name, then type. +- **Delete** — the trash icon. (You can't delete the last remaining page.) +- **Reorder** — drag a page to move it among its siblings. + +`[screenshot: the Pages sidebar with a nested sub-page]` + +Each page has its **own canvas**. Click a page to switch to it. Undo/redo and +saving cover the whole site, all pages together. + +--- + +## 8. The top navbar + +Every site can have a customizable **top navbar**. + +- Open **Navbar settings** (the gear) to adjust it. +- Add links that jump to a page or an external URL. +- **Rename link** / **Delete link** from each link's controls; drag to reorder. + +`[screenshot: the navbar with its settings popover open]` + +The navbar shows in both Edit and the read-only viewer, so visitors can navigate +your pages. + +--- + +## 9. Start from a template + +Don't start from a blank page unless you want to. In the toolbar, **Start from a +template** seeds the current page with a ready-made layout (Blank page, Landing +page, Dashboard, Article). Then just edit the widgets. + +--- + +## 10. Undo, redo, copy & paste + +- **Undo:** the toolbar button or `Ctrl/Cmd + Z`. +- **Redo:** the toolbar button or `Ctrl/Cmd + Shift + Z` (also `Ctrl/Cmd + Y`). +- **Copy / paste** a widget: `Ctrl/Cmd + C`, `Ctrl/Cmd + V`. +- **Duplicate:** `Ctrl/Cmd + D` (or the Duplicate button on a selected widget). +- **Delete:** `Delete` / `Backspace`. +- **Deselect:** `Escape`. + +(Shortcuts pause while you're typing in a text field, so they never eat your +keystrokes.) + +--- + +## 11. Preview, device width & grid density + +- **Edit / Preview** (toolbar): **Preview** renders the real page exactly as + visitors see it — no editing chrome. Switch back with **Edit**. +- **Device width:** the desktop / tablet / mobile buttons show how the page looks + at each width. +- **Grid density:** the zoom-out / `24×16` / zoom-in controls make the grid finer + or coarser for precise placement (or `Ctrl/Cmd + scroll` over the canvas). + +--- + +## 12. Saving + +Your work is saved back to the underlying document: + +- The toolbar shows a **status** — *All changes saved* / *Saving…* / *Unsaved + changes*. +- Edits **autosave** shortly after you make them. +- You can also press **Save** at any time. +- If you try to close the tab with unsaved changes, the browser warns you. + +There's no separate "publish" button — **saving is publishing**. The saved page +is the page your viewers see. + +--- + +## 13. Publishing / who can see it + +The site you build is stored as a normal document; **viewers see it live**. + +- To show a **finished, non-editable** site to end users, it's mounted in + **read-only** mode: they get the navbar, page navigation and content, but no + editing tools. +- Access can also be **gated by role** — editors get the builder, everyone else + gets the read-only viewer of the *same* page. + +Setting that up (recipes, roles, where it's stored) is an app-configuration task +covered in the developer README under +[**Embedding & hosting**](./README.md#embedding--hosting-internal). + +**Finding sites.** To browse *all* the sites that have been built (instead of +knowing each link), your app can show a **Site directory** — a gallery page that +lists every saved site and opens it in one click, with an optional **New site** +button to start a fresh one. See the developer README under +[**Finding sites — the Site directory**](./README.md#4-finding-sites--the-site-directory). + +--- + +## 14. Tips & troubleshooting + +- **My widget is just a grey box.** That's Edit mode. Switch to **Preview** to + see it for real. +- **I can't drop a widget where I want.** It would overlap another widget — move + the other one, resize, or make the grid finer (density). +- **My text/table is empty.** Select it and fill in the **Settings** section of + the Properties panel (type it in, or import a file for tables). +- **I can't edit — there's no toolbar.** You're in the read-only viewer; you may + not have an editing role for this page. +- **I want to undo everything.** Hold `Ctrl/Cmd + Z`; history covers the whole + session. + +--- + +## For developers + +Want a new kind of widget (a date picker, a rating, a map…)? It's a one-file +change — see **[ADDING_WIDGETS.md](./ADDING_WIDGETS.md)**. For how the builder is +embedded and hosted, see the **[README](./README.md)**. diff --git a/packages/dm-core-plugins/src/builder/hooks/useEditorShortcuts.ts b/packages/dm-core-plugins/src/builder/hooks/useEditorShortcuts.ts new file mode 100644 index 000000000..592aaf131 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/hooks/useEditorShortcuts.ts @@ -0,0 +1,107 @@ +import { useEffect } from 'react' + +type UseEditorShortcutsArgs = { + /** Shortcuts are active only while editing and not in the raw-JSON view. */ + enabled: boolean + selectedIndex: number | null + hasClipboard: boolean + onUndo: () => void + onRedo: () => void + onCopy: (index: number) => void + onPaste: () => void + onDuplicate: (index: number) => void + onDelete: (index: number) => void + onDeselect: () => void +} + +/** + * Keyboard shortcuts for the editor. Undo/redo are available whenever editing + * is active; the editing shortcuts (copy/paste/duplicate/delete/escape) are + * ignored while typing in a form field so the field's own behaviour (text undo, + * copy, etc.) keeps working. + */ +export const useEditorShortcuts = ({ + enabled, + selectedIndex, + hasClipboard, + onUndo, + onRedo, + onCopy, + onPaste, + onDuplicate, + onDelete, + onDeselect, +}: UseEditorShortcutsArgs): void => { + useEffect(() => { + if (!enabled) return + + const onKeyDown = (event: KeyboardEvent) => { + const target = event.target as HTMLElement | null + const typing = + !!target && + (target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.tagName === 'SELECT' || + target.isContentEditable) + const mod = event.metaKey || event.ctrlKey + const key = event.key.toLowerCase() + + if (mod && key === 'z') { + if (typing) return + event.preventDefault() + if (event.shiftKey) onRedo() + else onUndo() + return + } + if (mod && key === 'y') { + if (typing) return + event.preventDefault() + onRedo() + return + } + + if (typing) return + + if (mod && key === 'c') { + if (selectedIndex !== null) onCopy(selectedIndex) + return + } + if (mod && key === 'v') { + if (hasClipboard) { + event.preventDefault() + onPaste() + } + return + } + if (mod && key === 'd') { + if (selectedIndex !== null) { + event.preventDefault() + onDuplicate(selectedIndex) + } + return + } + if (key === 'delete' || key === 'backspace') { + if (selectedIndex !== null) { + event.preventDefault() + onDelete(selectedIndex) + } + return + } + if (key === 'escape') onDeselect() + } + + window.addEventListener('keydown', onKeyDown) + return () => window.removeEventListener('keydown', onKeyDown) + }, [ + enabled, + selectedIndex, + hasClipboard, + onUndo, + onRedo, + onCopy, + onPaste, + onDuplicate, + onDelete, + onDeselect, + ]) +} diff --git a/packages/dm-core-plugins/src/builder/hooks/usePersistence.ts b/packages/dm-core-plugins/src/builder/hooks/usePersistence.ts new file mode 100644 index 000000000..63f81b4d6 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/hooks/usePersistence.ts @@ -0,0 +1,171 @@ +import { type DmssAPI, splitAddress } from '@development-framework/dm-core' +import { useEffect, useMemo, useRef, useState } from 'react' +import { + deserializeSite, + isSerializedSite, + resolvePluginAliases, + serializeSite, + type TBuilderSite, +} from '../model/site' + +export type TSaveState = 'saved' | 'saving' | 'dirty' + +type UsePersistenceArgs = { + site: TBuilderSite + document: Record | null | undefined + idReference: string | undefined + dmssAPI: DmssAPI + locked: boolean + onChange?: (value: unknown) => void + notify: (message: string) => void + setSite: ( + updater: (current: TBuilderSite) => TBuilderSite, + label?: string | null + ) => void + /** Called once when a saved site is hydrated, so the editor can reset its + * page/section/selection to the loaded document. */ + onSiteLoaded: (site: TBuilderSite) => void +} + +type UsePersistenceResult = { + saveState: TSaveState + saveStatusLabel: string + save: () => Promise +} + +/** + * Owns loading and saving the site: initial hydration from the bound entity, + * the autosave debounce, the manual save, and the unsaved-changes guard. + * + * Builder widgets serialize free-form inline `config` objects with no DMSS + * `type`, which the validating update endpoint rejects. We therefore write + * through the context-free add-raw endpoint (the same path used to create a + * site), which stores the document verbatim and upserts by `_id`. + */ +export const usePersistence = ({ + site, + document, + idReference, + dmssAPI, + locked, + onChange, + notify, + setSite, + onSiteLoaded, +}: UsePersistenceArgs): UsePersistenceResult => { + // Persisted JSON of the whole site (all pages). The baseline ref tracks what + // was last saved so we can tell whether there are unsaved changes. + const currentJson = useMemo(() => JSON.stringify(serializeSite(site)), [site]) + const savedJsonRef = useRef(currentJson) + const [saveState, setSaveState] = useState('saved') + + // Hydrate the builder from the page entity's saved `layout` once it loads, so + // reopening shows previously saved work rather than the recipe seed. Accepts + // both the multi-page site format and a legacy single-grid layout. A corrupt + // or unreadable layout must never crash the editor: on failure we keep the + // seeded/empty site and tell the user, so their next save can recover it. + const hydratedRef = useRef(false) + useEffect(() => { + if (hydratedRef.current) return + // A Site-shaped document (top-level `pages`) *is* the site; otherwise the + // site is embedded in the host document's `layout` attribute (a page/host + // entity). Detecting by shape keeps existing `.layout` hosts unchanged. + const source = isSerializedSite(document) ? document : document?.layout + if (!source) return + hydratedRef.current = true + try { + const loaded = deserializeSite(source) + if (loaded.pages.length === 0) { + throw new Error('Saved layout has no pages') + } + savedJsonRef.current = JSON.stringify(serializeSite(loaded)) + setSite(() => loaded, 'load') + onSiteLoaded(loaded) + setSaveState('saved') + } catch { + // Leave the current (seeded) site in place and flag it as unsaved so the + // author can review before overwriting whatever unreadable data existed. + setSaveState('dirty') + notify('Could not read the saved page — starting from a blank layout') + } + }, [document, setSite, notify, onSiteLoaded]) + + /** + * Persist the current site. When the bound document *is* the site + * (Site-shaped) we write the navbar/pages onto the document itself with + * fully-qualified addresses (aliases can't be resolved on read), preserving + * the document's own `_id`, `type` and metadata. Otherwise the site is stored + * in the host document's `layout` attribute. Without an entity we fall back to + * the host `onChange` handler. Returns true on success. + */ + const save = async (): Promise => { + const serializedSite = JSON.parse(currentJson) + setSaveState('saving') + try { + if (idReference && document) { + const { dataSource } = splitAddress(idReference) + const body = isSerializedSite(document) + ? { + ...document, + ...resolvePluginAliases(serializedSite), + type: document.type, + } + : { ...document, layout: resolvePluginAliases(serializedSite) } + await dmssAPI.documentAddSimple({ dataSourceId: dataSource, body }) + } else if (onChange) { + onChange(serializedSite) + } else { + setSaveState('dirty') + return false + } + savedJsonRef.current = currentJson + setSaveState('saved') + return true + } catch { + setSaveState('dirty') + notify('Could not save page') + return false + } + } + + // Autosave: debounce changes and persist them. Without a save target the page + // just stays marked as having unsaved changes. + useEffect(() => { + if (locked) return + if (currentJson === savedJsonRef.current) { + setSaveState('saved') + return + } + if (!idReference && !onChange) { + setSaveState('dirty') + return + } + setSaveState('saving') + const timer = setTimeout(() => { + void save() + }, 800) + return () => clearTimeout(timer) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentJson, onChange, idReference, document, locked]) + + // Warn before leaving the page while there are unsaved changes. + useEffect(() => { + if (locked) return + if (saveState === 'saved') return + const handler = (event: BeforeUnloadEvent) => { + event.preventDefault() + event.returnValue = '' + } + window.addEventListener('beforeunload', handler) + return () => window.removeEventListener('beforeunload', handler) + }, [saveState, locked]) + + const saveStatusLabel = + saveState === 'saved' + ? 'All changes saved' + : saveState === 'saving' + ? 'Saving…' + : 'Unsaved changes' + + return { saveState, saveStatusLabel, save } +} diff --git a/packages/dm-core-plugins/src/builder/index.ts b/packages/dm-core-plugins/src/builder/index.ts new file mode 100644 index 000000000..571baa993 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/index.ts @@ -0,0 +1,52 @@ +/** + * Public entry for the drag-and-drop website builder plugin. + * + * Importing this module pulls in the editor component and its dependencies. For + * lightweight access to the persistence contract (serializer + types) without + * the editor, those are also re-exported from the package root. + * + * Everything not re-exported here is an internal implementation detail and may + * change without notice. + */ + +// The plugin component. +export { BuilderPlugin } from './BuilderPlugin' +export type { + TBuilderPage, + TBuilderSite, + TNavbar, + TNavbarItem, + TNavbarItemTarget, +} from './model/site' +// Persistence: the site model, its serializer and versioning. This is the +// contract stored on an entity's `layout` and validated by the Site blueprint. +export { + createDefaultNavbar, + createEmptySite, + createPage, + deserializeSite, + isSerializedSite, + NAVBAR_ITEM_TYPE, + NAVBAR_TYPE, + PAGE_TYPE, + SITE_SCHEMA_VERSION, + SITE_TYPE, + serializeSite, +} from './model/site' +// The site directory (gallery of saved sites + "New site"). +export { SiteDirectoryPlugin } from './SiteDirectoryPlugin' +// Plugin configuration. +export type { + TBlock, + TBlockCategory, + TBuilderMode, + TBuilderPluginConfig, + TInspectorField, + TInspectorFieldTarget, + TInspectorFieldType, +} from './types' +export type { TSiteDirectoryConfig } from './types/siteDirectory' +export type { TWidgetDefinition } from './widgets' +// The widget registry contract, so custom widgets can be authored and inspected +// programmatically. See docs/ADDING_WIDGETS.md for the authoring guide. +export { BLOCKS, getBlock, WIDGET_DEFINITIONS } from './widgets' diff --git a/packages/dm-core-plugins/src/builder/model/blocks.ts b/packages/dm-core-plugins/src/builder/model/blocks.ts new file mode 100644 index 000000000..3eed833c2 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/model/blocks.ts @@ -0,0 +1,11 @@ +/** + * The widget catalogue shown in the palette. + * + * This is now a thin re-export: every widget is defined in its own file under + * `builder/widgets/` and collected in `builder/widgets/index.ts`. `BLOCKS` and + * `getBlock` are derived from that registry so existing imports keep working. + * + * ★ To add a new widget, add a file under `builder/widgets/` — you do NOT edit + * this file. See `builder/docs/ADDING_WIDGETS.md`. + */ +export { BLOCKS, getBlock } from '../widgets' diff --git a/packages/dm-core-plugins/src/builder/model/history.ts b/packages/dm-core-plugins/src/builder/model/history.ts new file mode 100644 index 000000000..c646c219a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/model/history.ts @@ -0,0 +1,144 @@ +import { useCallback, useMemo, useState } from 'react' + +/** + * An undo/redo history of immutable snapshots. + * + * `present` is the current value; `past`/`future` are the stacks stepped through + * by undo/redo. `lastLabel` tracks the label of the most recent change so that + * consecutive changes sharing a label (e.g. dragging a resize handle, or typing + * into one field) coalesce into a single undo step instead of dozens. + */ +export type THistory = { + past: T[] + present: T + future: T[] + lastLabel: string | null +} + +/** Maximum number of undo steps kept; older steps are discarded. */ +export const HISTORY_LIMIT = 100 + +export const createHistory = (present: T): THistory => ({ + past: [], + present, + future: [], + lastLabel: null, +}) + +export const canUndo = (history: THistory): boolean => + history.past.length > 0 + +export const canRedo = (history: THistory): boolean => + history.future.length > 0 + +/** + * Record a new present value. + * + * - No-op when `next` is identical to the current present. + * - When `label` is non-null and equals the previous change's label, the present + * is replaced in place (the change coalesces, no new undo step is created). + * - Otherwise the current present is pushed onto the past, `future` is cleared, + * and the past is trimmed to `limit` entries. + */ +export const pushHistory = ( + history: THistory, + next: T, + label: string | null = null, + limit: number = HISTORY_LIMIT +): THistory => { + if (next === history.present) return history + + if (label !== null && label === history.lastLabel) { + return { ...history, present: next } + } + + const past = [...history.past, history.present] + return { + past: past.length > limit ? past.slice(past.length - limit) : past, + present: next, + future: [], + lastLabel: label, + } +} + +export const undoHistory = (history: THistory): THistory => { + if (!canUndo(history)) return history + const previous = history.past[history.past.length - 1] + return { + past: history.past.slice(0, -1), + present: previous, + future: [history.present, ...history.future], + lastLabel: null, + } +} + +export const redoHistory = (history: THistory): THistory => { + if (!canRedo(history)) return history + const next = history.future[0] + return { + past: [...history.past, history.present], + present: next, + future: history.future.slice(1), + lastLabel: null, + } +} + +/** End any coalescing run so the next change always starts a new undo step. */ +export const breakHistory = (history: THistory): THistory => + history.lastLabel === null ? history : { ...history, lastLabel: null } + +export type TUseHistory = { + present: T + canUndo: boolean + canRedo: boolean + /** Record a change. Pass a `label` to coalesce consecutive related changes. */ + set: (updater: T | ((current: T) => T), label?: string | null) => void + undo: () => void + redo: () => void + /** Replace the whole history with a fresh baseline (no undo across it). */ + reset: (value: T) => void + /** End the current coalescing run (e.g. on pointer-up or blur). */ + commit: () => void +} + +/** React state hook backed by an undo/redo history. */ +export const useHistory = (initial: T | (() => T)): TUseHistory => { + const [history, setHistory] = useState>(() => + createHistory( + typeof initial === 'function' ? (initial as () => T)() : initial + ) + ) + + const set = useCallback( + (updater: T | ((current: T) => T), label: string | null = null) => + setHistory((current) => + pushHistory( + current, + typeof updater === 'function' + ? (updater as (value: T) => T)(current.present) + : updater, + label + ) + ), + [] + ) + + const undo = useCallback(() => setHistory(undoHistory), []) + const redo = useCallback(() => setHistory(redoHistory), []) + const reset = useCallback((value: T) => setHistory(createHistory(value)), []) + const commit = useCallback(() => setHistory(breakHistory), []) + + return useMemo( + () => ({ + present: history.present, + canUndo: canUndo(history), + canRedo: canRedo(history), + set, + undo, + redo, + reset, + commit, + }), + [history, set, undo, redo, reset, commit] + ) +} diff --git a/packages/dm-core-plugins/src/builder/model/model.ts b/packages/dm-core-plugins/src/builder/model/model.ts new file mode 100644 index 000000000..26073409e --- /dev/null +++ b/packages/dm-core-plugins/src/builder/model/model.ts @@ -0,0 +1,623 @@ +import type { TUiRecipe, TViewConfig } from '@development-framework/dm-core' +import type { TGridArea, TGridItem, TGridSize } from '../../grid' +import type { TBlock, TBuilderModel } from '../types' +import { getBlock } from './blocks' + +/** DMSS type discriminators used by the runtime `grid` plugin entity format. */ +export const GRID_CONFIG_TYPE = 'PLUGINS:dm-core-plugins/grid/GridPluginConfig' +export const GRID_ITEM_TYPE = 'PLUGINS:dm-core-plugins/grid/GridItem' +export const GRID_AREA_TYPE = 'PLUGINS:dm-core-plugins/grid/GridArea' +export const GRID_SIZE_TYPE = 'PLUGINS:dm-core-plugins/grid/GridSize' +export const GRID_BORDER_TYPE = 'PLUGINS:dm-core-plugins/grid/GridBorder' +export const GRID_ITEM_STYLE_TYPE = 'PLUGINS:dm-core-plugins/grid/GridItemStyle' +export const REFERENCE_VIEW_CONFIG_TYPE = 'CORE:ReferenceViewConfig' +export const INLINE_RECIPE_VIEW_CONFIG_TYPE = 'CORE:InlineRecipeViewConfig' + +const DEFAULT_SIZE: TGridSize = { + columns: 24, + rows: 16, + rowGap: '8px', + columnGap: '8px', +} + +/** Create an empty builder model with a sensible default grid. */ +export const createEmptyModel = ( + size: Partial = {} +): TBuilderModel => ({ + size: { ...DEFAULT_SIZE, ...size }, + items: [], + itemBorder: { size: '1px', style: 'solid', color: '#bbb', radius: '5px' }, + showItemBorders: false, +}) + +/** True when two grid areas overlap (inclusive bounds). */ +export const areasOverlap = (a: TGridArea, b: TGridArea): boolean => + a.columnStart <= b.columnEnd && + b.columnStart <= a.columnEnd && + a.rowStart <= b.rowEnd && + b.rowStart <= a.rowEnd + +/** Clamp a grid area so it stays within the grid bounds. */ +export const clampArea = (area: TGridArea, size: TGridSize): TGridArea => { + const width = Math.min( + Math.max(0, area.columnEnd - area.columnStart), + size.columns - 1 + ) + const height = Math.min( + Math.max(0, area.rowEnd - area.rowStart), + size.rows - 1 + ) + const columnStart = Math.min( + Math.max(1, area.columnStart), + size.columns - width + ) + const rowStart = Math.min(Math.max(1, area.rowStart), size.rows - height) + return { + columnStart, + columnEnd: columnStart + width, + rowStart, + rowEnd: rowStart + height, + } +} + +/** Minimum and maximum grid density (cells per axis) when zooming. */ +export const MIN_GRID_CELLS = 4 +export const MAX_GRID_CELLS = 240 + +/** + * Rescale a grid's density (number of columns and rows) by `factor`, keeping + * the physical grid size constant. Widget areas are scaled proportionally so + * they stay in roughly the same visual place; a higher density yields a finer + * snap stride for micro adjustments. Nested section grids keep their own + * density and are not touched. Returns the model unchanged when the density + * would not actually change (e.g. clamped at a bound). + */ +export const rescaleGrid = ( + model: TBuilderModel, + factor: number +): TBuilderModel => { + if (!(factor > 0) || factor === 1) return model + const clampCells = (value: number): number => + Math.min(Math.max(MIN_GRID_CELLS, Math.round(value)), MAX_GRID_CELLS) + const columns = clampCells(model.size.columns * factor) + const rows = clampCells(model.size.rows * factor) + if (columns === model.size.columns && rows === model.size.rows) return model + + const fx = columns / model.size.columns + const fy = rows / model.size.rows + const size: TGridSize = { ...model.size, columns, rows } + const items = model.items.map((item) => { + const { gridArea } = item + const width = Math.max( + 1, + Math.round((gridArea.columnEnd - gridArea.columnStart + 1) * fx) + ) + const height = Math.max( + 1, + Math.round((gridArea.rowEnd - gridArea.rowStart + 1) * fy) + ) + const columnStart = Math.round((gridArea.columnStart - 1) * fx) + 1 + const rowStart = Math.round((gridArea.rowStart - 1) * fy) + 1 + const scaled: TGridArea = { + columnStart, + columnEnd: columnStart + width - 1, + rowStart, + rowEnd: rowStart + height - 1, + } + return { ...item, gridArea: clampArea(scaled, size) } + }) + return { ...model, size, items } +} + +export const findFreeArea = ( + model: TBuilderModel, + footprint: { columns: number; rows: number } +): TGridArea | null => { + const { columns, rows } = model.size + const width = Math.min(footprint.columns, columns) - 1 + const height = Math.min(footprint.rows, rows) - 1 + for (let rowStart = 1; rowStart + height <= rows; rowStart++) { + for (let columnStart = 1; columnStart + width <= columns; columnStart++) { + const candidate: TGridArea = { + rowStart, + rowEnd: rowStart + height, + columnStart, + columnEnd: columnStart + width, + } + if (!model.items.some((item) => areasOverlap(item.gridArea, candidate))) { + return candidate + } + } + } + return null +} + +const defaultViewConfig = (block: TBlock): TViewConfig => ({ + type: INLINE_RECIPE_VIEW_CONFIG_TYPE, + scope: '', + label: block.label, + recipe: { + name: block.id, + type: 'CORE:UiRecipe', + plugin: block.recipe, + ...(block.defaultConfig + ? { config: JSON.parse(JSON.stringify(block.defaultConfig)) } + : {}), + }, +}) + +const cloneViewConfig = (viewConfig: TViewConfig): TViewConfig => + JSON.parse(JSON.stringify(viewConfig)) + +/** + * Allocate a non-overlapping area for a widget of the given footprint. + * + * Uses the first free cell when one exists; otherwise grows the grid by extra + * rows and places the widget at the bottom, so widgets never overlap or get + * lost off-canvas. Returns the (possibly grown) grid size alongside the area. + */ +const allocateArea = ( + model: TBuilderModel, + footprint: { columns: number; rows: number } +): { size: TGridSize; area: TGridArea } => { + const free = findFreeArea(model, footprint) + if (free) return { size: model.size, area: free } + + const width = Math.min(footprint.columns, model.size.columns) - 1 + const height = footprint.rows - 1 + const rowStart = model.size.rows + 1 + return { + size: { ...model.size, rows: model.size.rows + footprint.rows }, + area: { + rowStart, + rowEnd: rowStart + height, + columnStart: 1, + columnEnd: 1 + width, + }, + } +} + +/** + * Add a widget for a block, returning a new model. + * + * When no explicit `area` is given the widget is placed in the first free cell; + * if the grid is full the grid grows by extra rows so widgets never overlap or + * get lost off-canvas. + */ +export const addWidget = ( + model: TBuilderModel, + block: TBlock, + area?: TGridArea, + viewConfig?: TViewConfig +): TBuilderModel => { + const makeItem = (gridArea: TGridArea): TGridItem => ({ + type: GRID_ITEM_TYPE, + gridArea, + viewConfig: viewConfig ?? defaultViewConfig(block), + ...(block.hideTitle ? {} : { title: block.label }), + }) + + if (area) { + return { + ...model, + items: [...model.items, makeItem(clampArea(area, model.size))], + } + } + + const { size, area: placed } = allocateArea(model, block.defaultSize) + return { ...model, size, items: [...model.items, makeItem(placed)] } +} + +/** Remove the widget at `index`, returning a new model. */ +export const removeWidget = ( + model: TBuilderModel, + index: number +): TBuilderModel => ({ + ...model, + items: model.items.filter((_, i) => i !== index), +}) + +/** Move the widget at `index` to a new (clamped) area, returning a new model. */ +export const moveWidget = ( + model: TBuilderModel, + index: number, + area: TGridArea +): TBuilderModel => ({ + ...model, + items: model.items.map((item, i) => + i === index ? { ...item, gridArea: clampArea(area, model.size) } : item + ), +}) + +/** Translate an area by a number of columns/rows, preserving its span. */ +export const translateArea = ( + area: TGridArea, + deltaColumns: number, + deltaRows: number +): TGridArea => ({ + columnStart: area.columnStart + deltaColumns, + columnEnd: area.columnEnd + deltaColumns, + rowStart: area.rowStart + deltaRows, + rowEnd: area.rowEnd + deltaRows, +}) + +/** + * Resize the widget at `index` to a new area, clamping to the grid bounds and + * enforcing a minimum footprint of 1x1 cell. Returns a new model. + */ +export const resizeWidget = ( + model: TBuilderModel, + index: number, + area: TGridArea +): TBuilderModel => { + const normalized: TGridArea = { + columnStart: area.columnStart, + columnEnd: Math.max(area.columnStart, area.columnEnd), + rowStart: area.rowStart, + rowEnd: Math.max(area.rowStart, area.rowEnd), + } + return { + ...model, + items: model.items.map((item, i) => + i === index + ? { ...item, gridArea: clampArea(normalized, model.size) } + : item + ), + } +} + +/** + * True when `area` overlaps any item other than the one at `excludeIndex`. + * Used to keep manual moves/resizes from stacking widgets on top of each other. + */ +export const wouldOverlap = ( + model: TBuilderModel, + excludeIndex: number, + area: TGridArea +): boolean => + model.items.some( + (item, i) => i !== excludeIndex && areasOverlap(item.gridArea, area) + ) + +/** + * Append a copy of `source` to the model, placing it in the first free area + * that fits it; if the grid is full it grows so the copy never overlaps another + * widget. The view config is deep-cloned so the copy is fully independent. + */ +export const insertWidgetItem = ( + model: TBuilderModel, + source: TGridItem +): TBuilderModel => { + const { gridArea } = source + const footprint = { + columns: gridArea.columnEnd - gridArea.columnStart + 1, + rows: gridArea.rowEnd - gridArea.rowStart + 1, + } + const { size, area: placed } = allocateArea(model, footprint) + const copy: TGridItem = { + type: GRID_ITEM_TYPE, + gridArea: placed, + viewConfig: cloneViewConfig(source.viewConfig), + ...(source.title ? { title: source.title } : {}), + } + return { ...model, size, items: [...model.items, copy] } +} + +/** + * Duplicate the widget at `index`. The copy is placed in the first free area + * that fits it; if the grid is full it grows so the copy never overlaps another + * widget. Returns a new model (unchanged when `index` is out of range). + */ +export const duplicateWidget = ( + model: TBuilderModel, + index: number +): TBuilderModel => { + const source = model.items[index] + if (!source) return model + return insertWidgetItem(model, source) +} + +/** Apply `updater` to the item at `index`, returning a new model. No-op when + * the index is out of range. */ +const updateItem = ( + model: TBuilderModel, + index: number, + updater: (item: TGridItem) => TGridItem +): TBuilderModel => { + if (!model.items[index]) return model + return { + ...model, + items: model.items.map((item, i) => (i === index ? updater(item) : item)), + } +} + +const updateViewConfig = ( + model: TBuilderModel, + index: number, + updater: (viewConfig: TViewConfig) => TViewConfig +): TBuilderModel => + updateItem(model, index, (item) => ({ + ...item, + viewConfig: updater(item.viewConfig), + })) + +/** Set the title shown above the widget. Returns a new model. */ +export const setWidgetTitle = ( + model: TBuilderModel, + index: number, + title: string +): TBuilderModel => updateItem(model, index, (item) => ({ ...item, title })) + +/** + * Set the widget's grid area from absolute coordinates (e.g. inspector layout + * fields), normalizing and clamping to the grid bounds. Returns a new model. + */ +export const setWidgetArea = ( + model: TBuilderModel, + index: number, + area: TGridArea +): TBuilderModel => resizeWidget(model, index, area) + +/** Set the widget's data binding (`viewConfig.scope`). Returns a new model. */ +export const setWidgetScope = ( + model: TBuilderModel, + index: number, + scope: string +): TBuilderModel => + updateViewConfig(model, index, (viewConfig) => ({ ...viewConfig, scope })) + +/** Set the widget's display label (`viewConfig.label`). Returns a new model. */ +export const setWidgetLabel = ( + model: TBuilderModel, + index: number, + label: string +): TBuilderModel => + updateViewConfig(model, index, (viewConfig) => ({ ...viewConfig, label })) + +/** + * Set a value on the widget's inline recipe config + * (`viewConfig.recipe.config[key]`). No-op when the widget uses a recipe + * reference (string) rather than an inline recipe. Returns a new model. + */ +export const setWidgetConfigValue = ( + model: TBuilderModel, + index: number, + key: string, + value: unknown +): TBuilderModel => + updateViewConfig(model, index, (viewConfig) => { + const recipe = viewConfig.recipe + if (!recipe || typeof recipe === 'string') return viewConfig + const config = { ...(recipe.config ?? {}), [key]: value } + return { ...viewConfig, recipe: { ...recipe, config } as TUiRecipe } + }) + +/** Read a value from a widget's inline recipe config, or `undefined`. */ +export const getWidgetConfigValue = (item: TGridItem, key: string): unknown => { + const recipe = item.viewConfig.recipe + if (!recipe || typeof recipe === 'string') return undefined + return recipe.config?.[key] +} + +/** + * Set a per-widget style property used by the grid renderer for presentation + * (text alignment, font size, color, etc). `target` selects whether to style + * the widget body (`style`) or its title (`titleStyle`). An empty value removes + * the key so it falls back to defaults. Returns a new model. + */ +export const setWidgetStyleValue = ( + model: TBuilderModel, + index: number, + key: keyof NonNullable, + value: unknown, + target: 'body' | 'title' = 'body' +): TBuilderModel => + updateItem(model, index, (item) => { + const prop = target === 'title' ? 'titleStyle' : 'style' + const style = { ...(item[prop] ?? {}) } + if (value === '' || value === undefined || value === null) delete style[key] + else (style as Record)[key] = value + return { ...item, [prop]: style } + }) + +/** Read a per-widget style property, or `undefined`. */ +export const getWidgetStyleValue = ( + item: TGridItem, + key: keyof NonNullable, + target: 'body' | 'title' = 'body' +): unknown => (target === 'title' ? item.titleStyle : item.style)?.[key] + +/** Runtime plugin name of the layout grid (used to detect container widgets). */ +export const GRID_PLUGIN_NAME = '@development-framework/dm-core-plugins/grid' + +/** True when the item is a layout container — its inline recipe is a grid. */ +export const isContainerItem = (item: TGridItem): boolean => { + const recipe = item.viewConfig.recipe + return ( + typeof recipe === 'object' && + recipe !== null && + recipe.plugin === GRID_PLUGIN_NAME + ) +} + +/** Coerce an arbitrary nested recipe config into a valid builder (grid) model. */ +export const ensureModel = (value: unknown): TBuilderModel => { + const base = createEmptyModel({ columns: 6, rows: 4 }) + if (!value || typeof value !== 'object') return base + const candidate = value as Partial + return { + size: { ...base.size, ...(candidate.size ?? {}) }, + items: Array.isArray(candidate.items) ? candidate.items : [], + itemBorder: { ...base.itemBorder, ...(candidate.itemBorder ?? {}) }, + showItemBorders: candidate.showItemBorders ?? base.showItemBorders, + } +} + +/** + * Get the nested grid model at `path`, where each path index drills into a + * container item's nested grid. An empty path returns the root model; an + * invalid path stops at the deepest valid level. + */ +export const getSubModel = ( + model: TBuilderModel, + path: number[] +): TBuilderModel => { + let current = model + for (const index of path) { + const item = current.items[index] + if (!item || !isContainerItem(item)) break + const recipe = item.viewConfig.recipe as TUiRecipe + current = ensureModel(recipe.config) + } + return current +} + +/** + * Truncate `path` to the deepest prefix that still resolves through genuine + * container items in `model`. Used to keep navigation valid after the model + * changes underneath it (e.g. an undo that removes the section being edited). + */ +export const clampPath = (model: TBuilderModel, path: number[]): number[] => { + const valid: number[] = [] + let current = model + for (const index of path) { + const item = current.items[index] + if (!item || !isContainerItem(item)) break + valid.push(index) + current = ensureModel((item.viewConfig.recipe as TUiRecipe).config) + } + return valid +} + +/** + * Immutably replace the nested grid model at `path`. An empty path replaces the + * root model. No-op when the path does not resolve to a container item. + */ +export const setSubModel = ( + model: TBuilderModel, + path: number[], + sub: TBuilderModel +): TBuilderModel => { + if (path.length === 0) return sub + const [index, ...rest] = path + const item = model.items[index] + if (!item || !isContainerItem(item)) return model + const recipe = item.viewConfig.recipe as TUiRecipe + const child = setSubModel(ensureModel(recipe.config), rest, sub) + return updateItem(model, index, (it) => ({ + ...it, + viewConfig: { + ...it.viewConfig, + recipe: { ...recipe, config: child } as TUiRecipe, + }, + })) +} + +/** + * Serialize a view config, recursing into Section containers so their nested + * grids carry the same DMSS `type` discriminators as the root. + */ +const serializeViewConfig = (item: TGridItem): unknown => { + if (!isContainerItem(item)) return item.viewConfig + const recipe = item.viewConfig.recipe as TUiRecipe + return { + ...item.viewConfig, + recipe: { ...recipe, config: serialize(ensureModel(recipe.config)) }, + } +} + +/** + * Self-contained widgets (button, heading, divider…) render their own content + * and must not carry the auto-generated title that data widgets get — a + * redundant title also overflows small cells and shows a scrollbar. Used at + * both serialize and deserialize time so neither the live preview nor older + * persisted pages show a stray title. + */ +export const blockHidesTitle = (viewConfig: any): boolean => { + const recipe = viewConfig?.recipe + const blockId = typeof recipe === 'string' ? recipe : recipe?.name + return getBlock(String(blockId))?.hideTitle === true +} + +/** + * Serialize the in-memory model to the canonical DMSS entity JSON consumed by + * the runtime `grid` plugin (adds the `type` discriminators). Section grids are + * serialized recursively so nested layouts round-trip and render at runtime. + */ +export const serialize = (model: TBuilderModel): Record => ({ + type: GRID_CONFIG_TYPE, + size: { type: GRID_SIZE_TYPE, ...model.size }, + items: model.items.map((item) => ({ + type: GRID_ITEM_TYPE, + gridArea: { type: GRID_AREA_TYPE, ...item.gridArea }, + viewConfig: serializeViewConfig(item), + ...(item.title && !blockHidesTitle(item.viewConfig) + ? { title: item.title } + : {}), + ...(item.style && Object.keys(item.style).length + ? { style: { type: GRID_ITEM_STYLE_TYPE, ...item.style } } + : {}), + ...(item.titleStyle && Object.keys(item.titleStyle).length + ? { titleStyle: { type: GRID_ITEM_STYLE_TYPE, ...item.titleStyle } } + : {}), + })), + itemBorder: { type: GRID_BORDER_TYPE, ...model.itemBorder }, + showItemBorders: model.showItemBorders, +}) + +/** + * Rebuild a view config from stored JSON, recursing into Section containers so + * their nested grids return to raw builder models the editor can mutate. + */ +const deserializeViewConfig = (viewConfig: any): TViewConfig => { + const recipe = viewConfig?.recipe + if ( + recipe && + typeof recipe === 'object' && + recipe.plugin === GRID_PLUGIN_NAME + ) { + return { + ...viewConfig, + recipe: { ...recipe, config: deserialize(recipe.config) }, + } + } + return viewConfig +} + +/** Rebuild the in-memory model from a (possibly partial) stored grid config. */ +export const deserialize = (entity: any): TBuilderModel => { + const base = createEmptyModel() + if (!entity || typeof entity !== 'object') return base + const { type: _sizeType, ...size } = entity.size ?? {} + return { + size: { ...base.size, ...size }, + items: Array.isArray(entity.items) + ? entity.items.map((item: any): TGridItem => { + const { type: _areaType, ...gridArea } = item.gridArea ?? {} + const stripStyle = (raw: any): TGridItem['style'] | undefined => { + if (!raw) return undefined + const { type: _styleType, ...rest } = raw + return Object.keys(rest).length + ? (rest as TGridItem['style']) + : undefined + } + const style = stripStyle(item.style) + const titleStyle = stripStyle(item.titleStyle) + const keepTitle = item.title && !blockHidesTitle(item.viewConfig) + return { + type: GRID_ITEM_TYPE, + gridArea: gridArea as TGridArea, + viewConfig: deserializeViewConfig(item.viewConfig), + ...(keepTitle ? { title: item.title } : {}), + ...(style ? { style } : {}), + ...(titleStyle ? { titleStyle } : {}), + } + }) + : [], + itemBorder: (() => { + const { type: _borderType, ...border } = entity.itemBorder ?? {} + return { ...base.itemBorder, ...border } + })(), + showItemBorders: entity.showItemBorders ?? base.showItemBorders, + } +} diff --git a/packages/dm-core-plugins/src/builder/model/site.ts b/packages/dm-core-plugins/src/builder/model/site.ts new file mode 100644 index 000000000..f6257f4ac --- /dev/null +++ b/packages/dm-core-plugins/src/builder/model/site.ts @@ -0,0 +1,696 @@ +import type { TBuilderModel } from '../types' +import { createEmptyModel, deserialize, serialize } from './model' + +/** DMSS type discriminators for the multi-page site wrapper. */ +export const SITE_TYPE = 'PLUGINS:dm-core-plugins/builder/Site' +export const PAGE_TYPE = 'PLUGINS:dm-core-plugins/builder/Page' +export const NAVBAR_TYPE = 'PLUGINS:dm-core-plugins/builder/Navbar' +export const NAVBAR_ITEM_TYPE = 'PLUGINS:dm-core-plugins/builder/NavbarItem' + +/** + * The `PLUGINS:` alias (declared in each package's dependencies) resolves to the + * `system/Plugins` package. dm-cli import and the running app resolve it via + * package context, but the raw DMSS search and add-raw endpoints do NOT — they + * need the fully-qualified `dmss://…` address. Keep both forms so the builder + * can emit aliases for storage (where context resolves them) and full addresses + * where it talks to those context-free endpoints. + */ +export const PLUGINS_ALIAS_PREFIX = 'PLUGINS:' +export const PLUGINS_ADDRESS_PREFIX = 'dmss://system/Plugins/' + +/** + * The `CORE:` alias resolves to the `system/SIMOS` package (where the standard + * blueprints like `UiRecipe` and `InlineRecipeViewConfig` live). Serialized + * widget view configs embed these `CORE:` types, so — exactly like `PLUGINS:` — + * they must be expanded to fully-qualified addresses before a site is written + * through the context-free add-raw endpoint. A stored `CORE:` alias cannot be + * resolved when DMSS reads the document back, which makes every widget's + * `attributeGet` fail and the viewer spin forever. + */ +export const CORE_ALIAS_PREFIX = 'CORE:' +export const CORE_ADDRESS_PREFIX = 'dmss://system/SIMOS/' + +/** + * Type stamped onto every free-form widget `config` object before a site is + * written to storage. + * + * Widget configs are deliberately typeless (`{ text, level, align }`, etc.) so + * authors can add any widget without a bespoke blueprint. They are stored under + * the core `UiRecipe.config` attribute, which DMSS declares as `object` (a + * non-primitive type). When DMSS builds the document node-tree — which it does + * on *delete* (and any full read) — it recurses into every `object` attribute + * and throws `Entity is missing required attribute 'type'` for our typeless + * configs, so sites containing widgets cannot be deleted. + * + * `dmss://system/SIMOS/Entity` is the generic base type whose only required + * attribute is `type` (everything else is optional) and whose sole non-primitive + * attribute, `_meta_`, no widget uses. Stamping it onto each config lets DMSS + * resolve the node-tree (the arbitrary config keys are simply carried through as + * unmodelled data) while remaining invisible to the widgets, which ignore it. + */ +export const WIDGET_CONFIG_TYPE = 'dmss://system/SIMOS/Entity' + +/** + * Stamp {@link WIDGET_CONFIG_TYPE} onto every `config` object that lacks a + * `type`, in place. Only bare `config` objects are touched; configs that + * already declare a type are left as-is (idempotent), and config *values* + * (e.g. a chart's `rows`) are never modelled by `Entity`, so DMSS leaves them + * untouched too. + */ +export const stampWidgetConfigTypes = (value: unknown): void => { + if (Array.isArray(value)) { + for (const item of value) stampWidgetConfigTypes(item) + return + } + if (!value || typeof value !== 'object') return + const obj = value as Record + for (const [key, child] of Object.entries(obj)) { + if ( + key === 'config' && + child && + typeof child === 'object' && + !Array.isArray(child) && + typeof (child as Record).type !== 'string' + ) { + ;(child as Record).type = WIDGET_CONFIG_TYPE + } + stampWidgetConfigTypes(child) + } +} + +/** + * Fully-qualified address of the Site blueprint, for the context-free search + * endpoint (which cannot resolve the `PLUGINS:` alias). + */ +export const SITE_TYPE_ADDRESS = SITE_TYPE.replace( + PLUGINS_ALIAS_PREFIX, + PLUGINS_ADDRESS_PREFIX +) + +/** + * Replace every builder alias (`PLUGINS:` and `CORE:`) in a serialized value + * with its fully-qualified `dmss://…` address, and stamp a resolvable `type` + * onto every typeless widget `config`, so documents written through the + * context-free add-raw / update endpoints carry resolvable type discriminators. + * Without the alias expansion, a stored alias can't be resolved when the + * document is read back and rendering fails (search misses, or the viewer spins + * on `attributeGet`); without the config `type`, DMSS cannot build the node-tree + * and the site can't be deleted (see {@link WIDGET_CONFIG_TYPE}). + */ +export const resolvePluginAliases = (value: T): T => { + const resolved = JSON.parse( + JSON.stringify(value) + .split(PLUGINS_ALIAS_PREFIX) + .join(PLUGINS_ADDRESS_PREFIX) + .split(CORE_ALIAS_PREFIX) + .join(CORE_ADDRESS_PREFIX) + ) + stampWidgetConfigTypes(resolved) + return resolved +} + +/** + * Version of the serialized site format. Bump this whenever the stored shape + * changes in a way that needs migrating, and add a matching case to + * `migrateSite` so older saved sites keep loading. + */ +export const SITE_SCHEMA_VERSION = 1 + +/** + * Where a navbar link points. `page` links navigate within the site (SPA-style) + * to one of its pages; `url` links open an external address. + */ +export type TNavbarItemTarget = + | { kind: 'page'; pageId: string } + | { kind: 'url'; href: string } + +/** A single clickable link shown in the top navbar. */ +export type TNavbarItem = { + id: string + label: string + target: TNavbarItemTarget +} + +/** + * The site's customizable top navigation bar, shared across every page (like a + * real website header). Rendered in both edit and preview so the canvas stays + * WYSIWYG. `enabled` is false by default so existing sites are unchanged until + * the author opts in via the "Add navbar" affordance. + */ +export type TNavbar = { + enabled: boolean + brand: string + background: string + color: string + brandColor: string + /** Horizontal placement of the link group within the bar. */ + align: 'left' | 'center' | 'right' + items: TNavbarItem[] +} + +/** + * A single navigable page in the site. `id` is a stable handle used by the nav + * sidebar to switch/route; `title` doubles as the nav label; `layout` is the + * page's grid model (the same shape the runtime grid plugin consumes). + * + * `children` are sub-pages reachable from this page's nav menu, nestable to any + * depth so the sidebar can render an infinitely deep menu tree. + */ +export type TBuilderPage = { + id: string + title: string + layout: TBuilderModel + children: TBuilderPage[] +} + +/** + * A website built in the builder: an ordered tree of pages reachable from the + * nav sidebar, plus a customizable top navbar shared by every page. There is + * always at least one top-level page. + * + * `title` is a human-friendly display name shown on the site directory card + * (the stored DMSS document `name` must stay slug-safe, so the freeform title + * lives here instead). `published` gates whether the site is treated as live: + * the directory only surfaces published sites to visitors, while authors keep + * seeing their drafts so they can finish and publish them. + */ +export type TBuilderSite = { + title: string + published: boolean + navbar: TNavbar + pages: TBuilderPage[] +} + +/** Where a page sits in the tree: its siblings, index, and parent (null=root). */ +export type TPageContext = { + page: TBuilderPage + parentId: string | null + siblings: TBuilderPage[] + index: number +} + +let pageCounter = 0 + +/** Generate a reasonably unique, stable page id. */ +export const createPageId = (): string => { + pageCounter += 1 + return `page-${Date.now().toString(36)}-${pageCounter}` +} + +let navItemCounter = 0 + +/** Generate a reasonably unique, stable navbar item id. */ +export const createNavItemId = (): string => { + navItemCounter += 1 + return `nav-${Date.now().toString(36)}-${navItemCounter}` +} + +/** Create a page with the given title and (optionally) an existing layout. */ +export const createPage = ( + title: string, + layout: TBuilderModel = createEmptyModel() +): TBuilderPage => ({ id: createPageId(), title, layout, children: [] }) + +/** A sensible, disabled-by-default navbar (opt-in via the builder UI). */ +export const createDefaultNavbar = (): TNavbar => ({ + enabled: false, + brand: 'My Site', + background: '#ffffff', + color: '#3d3d3d', + brandColor: '#007079', + align: 'right', + items: [], +}) + +/** Create a site seeded with a single empty "Home" page and a default navbar. */ +export const createEmptySite = (): TBuilderSite => ({ + title: '', + published: false, + navbar: createDefaultNavbar(), + pages: [createPage('Home')], +}) + +/** True when a stored value is the serialized multi-page site wrapper. */ +export const isSerializedSite = (raw: unknown): boolean => + !!raw && + typeof raw === 'object' && + Array.isArray((raw as { pages?: unknown }).pages) + +const serializePage = (page: TBuilderPage): Record => ({ + type: PAGE_TYPE, + id: page.id, + title: page.title, + layout: serialize(page.layout), + children: page.children.map(serializePage), +}) + +const serializeNavItem = (item: TNavbarItem): Record => ({ + type: NAVBAR_ITEM_TYPE, + id: item.id, + label: item.label, + targetKind: item.target.kind, + ...(item.target.kind === 'page' + ? { pageId: item.target.pageId } + : { href: item.target.href }), +}) + +const serializeNavbar = (navbar: TNavbar): Record => ({ + type: NAVBAR_TYPE, + enabled: navbar.enabled, + brand: navbar.brand, + background: navbar.background, + color: navbar.color, + brandColor: navbar.brandColor, + align: navbar.align, + items: navbar.items.map(serializeNavItem), +}) + +/** Serialize a site to canonical DMSS JSON (adds `type` discriminators). */ +export const serializeSite = (site: TBuilderSite): Record => ({ + type: SITE_TYPE, + schemaVersion: SITE_SCHEMA_VERSION, + title: site.title, + published: site.published, + navbar: serializeNavbar(site.navbar), + pages: site.pages.map(serializePage), +}) + +const deserializeNavItem = (raw: Record): TNavbarItem => { + const target: TNavbarItemTarget = + raw.targetKind === 'url' + ? { kind: 'url', href: typeof raw.href === 'string' ? raw.href : '' } + : { + kind: 'page', + pageId: typeof raw.pageId === 'string' ? raw.pageId : '', + } + return { + id: typeof raw.id === 'string' ? raw.id : createNavItemId(), + label: typeof raw.label === 'string' ? raw.label : 'Link', + target, + } +} + +/** + * Rebuild a navbar from stored JSON, falling back to the default for any + * missing field so sites authored before the navbar existed still load. + */ +const deserializeNavbar = (raw: unknown): TNavbar => { + const base = createDefaultNavbar() + if (!raw || typeof raw !== 'object') return base + const obj = raw as Record + return { + enabled: typeof obj.enabled === 'boolean' ? obj.enabled : base.enabled, + brand: typeof obj.brand === 'string' ? obj.brand : base.brand, + background: + typeof obj.background === 'string' ? obj.background : base.background, + color: typeof obj.color === 'string' ? obj.color : base.color, + brandColor: + typeof obj.brandColor === 'string' ? obj.brandColor : base.brandColor, + align: + obj.align === 'left' || obj.align === 'center' || obj.align === 'right' + ? obj.align + : base.align, + items: Array.isArray(obj.items) + ? obj.items + .filter((item): item is Record => !!item) + .map(deserializeNavItem) + : [], + } +} + +const deserializePage = (raw: Record): TBuilderPage => ({ + id: typeof raw.id === 'string' ? raw.id : createPageId(), + title: typeof raw.title === 'string' ? raw.title : 'Page', + layout: deserialize(raw.layout), + children: Array.isArray(raw.children) + ? raw.children + .filter((child): child is Record => !!child) + .map(deserializePage) + : [], +}) + +/** + * Read the stored `schemaVersion`, defaulting to 1 for sites saved before the + * field existed (or legacy single-grid layouts). + */ +const readSchemaVersion = (raw: unknown): number => { + if (raw && typeof raw === 'object') { + const value = (raw as { schemaVersion?: unknown }).schemaVersion + if (typeof value === 'number' && Number.isFinite(value)) return value + } + return 1 +} + +/** Upgrades a stored payload from one schema version to the next. */ +type TSiteMigration = (raw: Record) => Record + +/** + * Ordered migrations keyed by the version they upgrade *from*. Empty today + * (every supported version loads directly); when the stored shape changes, bump + * `SITE_SCHEMA_VERSION` and add an entry here, e.g. `1: migrateV1toV2`. + */ +const SITE_MIGRATIONS: Record = {} + +/** + * Migrate a stored site payload up to the current `SITE_SCHEMA_VERSION` by + * applying each registered migration in turn, so older saved sites keep loading + * after the format evolves. + */ +const migrateSite = (raw: Record): Record => { + let current = raw + let version = readSchemaVersion(current) + while (version < SITE_SCHEMA_VERSION && SITE_MIGRATIONS[version]) { + current = SITE_MIGRATIONS[version](current) + version = readSchemaVersion(current) + } + return current +} + +/** + * Rebuild a site from stored JSON. Accepts either the multi-page wrapper or a + * legacy single-grid layout (which is wrapped into a one-page "Home" site), so + * pages authored before multi-page support still load. Legacy pages without a + * `children` array deserialize as leaf pages; a missing navbar defaults to the + * disabled default so older sites are unchanged. Older `schemaVersion`s are + * migrated forward via `migrateSite` before deserializing. + * + * `title` falls back to the stored document `name` (the slug) so sites authored + * before the display-name field existed still show a sensible label, and + * `published` defaults to false so sites without the flag are treated as drafts + * until an author explicitly publishes them. + */ +export const deserializeSite = (raw: unknown): TBuilderSite => { + if (isSerializedSite(raw)) { + const migrated = migrateSite(raw as Record) + const source = migrated as { + pages: unknown[] + navbar?: unknown + title?: unknown + name?: unknown + published?: unknown + } + const navbar = deserializeNavbar(source.navbar) + const pages = source.pages + .filter((page): page is Record => !!page) + .map(deserializePage) + const title = + typeof source.title === 'string' + ? source.title + : typeof source.name === 'string' + ? source.name + : '' + const published = source.published === true + return pages.length > 0 + ? { title, published, navbar, pages } + : createEmptySite() + } + // Legacy or seed: a single grid layout becomes the site's only page. + return { + title: '', + published: false, + navbar: createDefaultNavbar(), + pages: [createPage('Home', deserialize(raw))], + } +} + +/** Index of the top-level page with `id`, or -1 when absent. */ +export const findPageIndex = (site: TBuilderSite, id: string): number => + site.pages.findIndex((page) => page.id === id) + +/** Locate a page anywhere in the tree along with its sibling context. */ +export const findPageContext = ( + site: TBuilderSite, + id: string +): TPageContext | null => { + const walk = ( + siblings: TBuilderPage[], + parentId: string | null + ): TPageContext | null => { + for (let index = 0; index < siblings.length; index += 1) { + const page = siblings[index] + if (page.id === id) return { page, parentId, siblings, index } + const nested = walk(page.children, page.id) + if (nested) return nested + } + return null + } + return walk(site.pages, null) +} + +/** Find a page anywhere in the tree, or null when absent. */ +export const findPage = (site: TBuilderSite, id: string): TBuilderPage | null => + findPageContext(site, id)?.page ?? null + +/** The chain of pages from the root down to (and including) `id`, or []. */ +export const findPagePath = ( + site: TBuilderSite, + id: string +): TBuilderPage[] => { + const walk = ( + siblings: TBuilderPage[], + trail: TBuilderPage[] + ): TBuilderPage[] | null => { + for (const page of siblings) { + const next = [...trail, page] + if (page.id === id) return next + const nested = walk(page.children, next) + if (nested) return nested + } + return null + } + return walk(site.pages, []) ?? [] +} + +/** + * Apply `updater` to the page with `id` wherever it sits in the tree, returning + * a new page list (or the same reference when nothing changed). Sharing this + * walker keeps every mutation immutable and structurally minimal. + */ +const updatePageInList = ( + pages: TBuilderPage[], + id: string, + updater: (page: TBuilderPage) => TBuilderPage +): TBuilderPage[] => { + let changed = false + const next = pages.map((page) => { + if (page.id === id) { + const updated = updater(page) + if (updated !== page) changed = true + return updated + } + const children = updatePageInList(page.children, id, updater) + if (children !== page.children) { + changed = true + return { ...page, children } + } + return page + }) + return changed ? next : pages +} + +const uniqueChildTitle = (siblings: TBuilderPage[], title?: string): string => { + if (title) return title + const used = new Set(siblings.map((page) => page.title)) + let n = siblings.length + 1 + while (used.has(`Page ${n}`)) n += 1 + return `Page ${n}` +} + +/** + * Append a new empty page and return the updated site plus the new page id. + * With `parentId` the page is added as a sub-page of that parent; otherwise it + * becomes a new top-level page. + */ +export const addPage = ( + site: TBuilderSite, + parentId: string | null = null, + title?: string +): { site: TBuilderSite; id: string } => { + if (!parentId) { + const page = createPage(uniqueChildTitle(site.pages, title)) + return { site: { ...site, pages: [...site.pages, page] }, id: page.id } + } + let newId = '' + const pages = updatePageInList(site.pages, parentId, (parent) => { + const page = createPage(uniqueChildTitle(parent.children, title)) + newId = page.id + return { ...parent, children: [...parent.children, page] } + }) + if (!newId) return { site, id: '' } + return { site: { ...site, pages }, id: newId } +} + +/** Immutably rename a page anywhere in the tree; no-op for an unknown id. */ +export const renamePage = ( + site: TBuilderSite, + id: string, + title: string +): TBuilderSite => { + const pages = updatePageInList(site.pages, id, (page) => ({ ...page, title })) + return pages === site.pages ? site : { ...site, pages } +} + +const removeFromList = (pages: TBuilderPage[], id: string): TBuilderPage[] => { + let changed = false + const next: TBuilderPage[] = [] + for (const page of pages) { + if (page.id === id) { + changed = true + continue + } + const children = removeFromList(page.children, id) + if (children !== page.children) { + changed = true + next.push({ ...page, children }) + } else { + next.push(page) + } + } + return changed ? next : pages +} + +/** + * Immutably remove a page (and its entire sub-tree). The last remaining + * top-level page is never removed (a site always keeps at least one page), so + * the call is a no-op in that case. Sub-pages are always removable. + */ +export const removePage = (site: TBuilderSite, id: string): TBuilderSite => { + const isRoot = site.pages.some((page) => page.id === id) + if (isRoot && site.pages.length <= 1) return site + const pages = removeFromList(site.pages, id) + return pages === site.pages ? site : { ...site, pages } +} + +/** Replace a page's grid layout immutably; no-op for an unknown id. */ +export const setPageLayout = ( + site: TBuilderSite, + id: string, + layout: TBuilderModel +): TBuilderSite => { + const pages = updatePageInList(site.pages, id, (page) => + page.layout === layout ? page : { ...page, layout } + ) + return pages === site.pages ? site : { ...site, pages } +} + +const reorder = (list: T[], from: number, to: number): T[] => { + const count = list.length + if (from < 0 || from >= count) return list + const target = Math.min(Math.max(0, to), count - 1) + if (target === from) return list + const next = [...list] + const [moved] = next.splice(from, 1) + next.splice(target, 0, moved) + return next +} + +/** + * Move a page within its sibling list from index `from` to `to` (both clamped). + * `parentId` selects the sibling list (null = top-level). Used by the sidebar to + * reorder nav items at a single level. + */ +export const movePage = ( + site: TBuilderSite, + parentId: string | null, + from: number, + to: number +): TBuilderSite => { + if (!parentId) { + const pages = reorder(site.pages, from, to) + return pages === site.pages ? site : { ...site, pages } + } + const pages = updatePageInList(site.pages, parentId, (parent) => { + const children = reorder(parent.children, from, to) + return children === parent.children ? parent : { ...parent, children } + }) + return pages === site.pages ? site : { ...site, pages } +} + +// ---- Site metadata ------------------------------------------------------ + +/** + * Immutably update the site's display `title` and/or `published` flag. Used by + * the toolbar's name field and Publish toggle; a no-op when nothing changes so + * it never adds a spurious undo step. + */ +export const updateSiteMeta = ( + site: TBuilderSite, + patch: Partial> +): TBuilderSite => { + const next = { ...site, ...patch } + return next.title === site.title && next.published === site.published + ? site + : next +} + +// ---- Navbar ------------------------------------------------------------- + +/** Immutably merge `patch` into the site's navbar. */ +export const updateNavbar = ( + site: TBuilderSite, + patch: Partial +): TBuilderSite => ({ ...site, navbar: { ...site.navbar, ...patch } }) + +/** + * Append a navbar link and return the updated site plus the new item id. The + * link defaults to the first page when one exists, otherwise an empty URL, so a + * freshly added item is immediately editable and valid. + */ +export const addNavItem = ( + site: TBuilderSite, + target?: TNavbarItemTarget +): { site: TBuilderSite; id: string } => { + const firstPage = site.pages[0] + const resolved: TNavbarItemTarget = + target ?? + (firstPage + ? { kind: 'page', pageId: firstPage.id } + : { kind: 'url', href: '' }) + const label = + resolved.kind === 'page' + ? (findPage(site, resolved.pageId)?.title ?? 'Link') + : 'Link' + const item: TNavbarItem = { id: createNavItemId(), label, target: resolved } + return { + site: { + ...site, + navbar: { ...site.navbar, items: [...site.navbar.items, item] }, + }, + id: item.id, + } +} + +/** Immutably merge `patch` into the navbar item with `id`; no-op if unknown. */ +export const updateNavItem = ( + site: TBuilderSite, + id: string, + patch: Partial +): TBuilderSite => { + let changed = false + const items = site.navbar.items.map((item) => { + if (item.id !== id) return item + changed = true + return { ...item, ...patch } + }) + return changed ? { ...site, navbar: { ...site.navbar, items } } : site +} + +/** Immutably remove the navbar item with `id`; no-op if unknown. */ +export const removeNavItem = (site: TBuilderSite, id: string): TBuilderSite => { + const items = site.navbar.items.filter((item) => item.id !== id) + return items.length === site.navbar.items.length + ? site + : { ...site, navbar: { ...site.navbar, items } } +} + +/** Move a navbar item from index `from` to `to` (both clamped). */ +export const moveNavItem = ( + site: TBuilderSite, + from: number, + to: number +): TBuilderSite => { + const items = reorder(site.navbar.items, from, to) + return items === site.navbar.items + ? site + : { ...site, navbar: { ...site.navbar, items } } +} diff --git a/packages/dm-core-plugins/src/builder/model/templates.ts b/packages/dm-core-plugins/src/builder/model/templates.ts new file mode 100644 index 000000000..8cd7994a4 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/model/templates.ts @@ -0,0 +1,113 @@ +import type { TBuilderModel } from '../types' +import { getBlock } from './blocks' +import { addWidget, createEmptyModel } from './model' + +const block = (id: string) => { + const found = getBlock(id) + if (!found) throw new Error(`Unknown block: ${id}`) + return found +} + +export type TTemplate = { + id: string + label: string + description: string + build: () => TBuilderModel +} + +/** + * Starter page presets. Each builds a fresh model by placing widgets at fixed + * grid areas, so applying a template yields a ready-to-edit layout. + */ +export const TEMPLATES: TTemplate[] = [ + { + id: 'blank', + label: 'Blank page', + description: 'An empty canvas.', + build: () => createEmptyModel(), + }, + { + id: 'landing', + label: 'Landing page', + description: 'A hero heading, a media banner and a supporting text column.', + build: () => { + let model = createEmptyModel() + model = addWidget(model, block('text'), { + rowStart: 1, + rowEnd: 2, + columnStart: 1, + columnEnd: 12, + }) + model = addWidget(model, block('image'), { + rowStart: 3, + rowEnd: 6, + columnStart: 1, + columnEnd: 8, + }) + model = addWidget(model, block('text'), { + rowStart: 3, + rowEnd: 6, + columnStart: 9, + columnEnd: 12, + }) + return model + }, + }, + { + id: 'dashboard', + label: 'Dashboard', + description: 'A header with two side-by-side data tables.', + build: () => { + let model = createEmptyModel() + model = addWidget(model, block('text'), { + rowStart: 1, + rowEnd: 1, + columnStart: 1, + columnEnd: 12, + }) + model = addWidget(model, block('table'), { + rowStart: 2, + rowEnd: 5, + columnStart: 1, + columnEnd: 6, + }) + model = addWidget(model, block('table'), { + rowStart: 2, + rowEnd: 5, + columnStart: 7, + columnEnd: 12, + }) + return model + }, + }, + { + id: 'article', + label: 'Article', + description: 'A title, a lead image and a body text column.', + build: () => { + let model = createEmptyModel() + model = addWidget(model, block('text'), { + rowStart: 1, + rowEnd: 1, + columnStart: 3, + columnEnd: 10, + }) + model = addWidget(model, block('image'), { + rowStart: 2, + rowEnd: 4, + columnStart: 3, + columnEnd: 10, + }) + model = addWidget(model, block('text'), { + rowStart: 5, + rowEnd: 8, + columnStart: 3, + columnEnd: 10, + }) + return model + }, + }, +] + +export const getTemplate = (id: string): TTemplate | undefined => + TEMPLATES.find((template) => template.id === id) diff --git a/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.styles.ts b/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.styles.ts new file mode 100644 index 000000000..2b392db74 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.styles.ts @@ -0,0 +1,152 @@ +import styled from 'styled-components' + +export const Container = styled.div` + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + box-sizing: border-box; +` + +export const GridEditor = styled.div` + overflow: auto; + flex: 1; + border: 1px solid #d8d8d8; + border-radius: 4px; +` + +export const GridTable = styled.table` + border-collapse: collapse; + width: 100%; + min-width: max-content; +` + +export const GridTh = styled.th` + padding: 4px 8px; + background: #f5f5f5; + border: 1px solid #d8d8d8; + font-weight: 600; + font-size: 13px; + text-align: left; + white-space: nowrap; +` + +export const GridTd = styled.td` + padding: 0; + border: 1px solid #d8d8d8; + min-width: 80px; +` + +export const ActionTd = styled.td` + padding: 2px 4px; + border: 1px solid #d8d8d8; + width: 28px; + text-align: center; + vertical-align: middle; +` + +export const CellInput = styled.input` + width: 100%; + box-sizing: border-box; + border: none; + outline: none; + padding: 5px 8px; + font: inherit; + font-size: 13px; + background: transparent; + text-align: center; + + &:focus { + background: #eef6ff; + } +` + +export const SelectRowButton = styled.button` + border: none; + background: none; + cursor: pointer; + color: #aaa; + padding: 2px 4px; + line-height: 1; + font-size: 14px; + + &:hover { + color: #c00; + } +` + +export const Toolbar = styled.div` + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; +` + +export const AddRowButton = styled.button` + padding: 4px 12px; + border: 1px solid #00c424; + border-radius: 4px; + background: #fafafa; + cursor: pointer; + font-size: 12px; + color: #00c424; + + &:hover { + background: #00c424; + color: #fafaaf; + cursor: pointer; + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +` +export const DeleteRowButton = styled.button` + padding: 4px 12px; + border: 1px solid rgb(255, 0, 0); + border-radius: 4px; + background: #fafafa; + cursor: pointer; + font-size: 12px; + color: rgb(255, 0, 0); + + &:hover { + background: rgb(255, 0, 0); + color: #fafaaf; + cursor: pointer; + } + + &:disabled { + opacity: 0.5; + cursor: default; + } +` + +export const SaveButton = styled.button<{ $saving: boolean }>` + padding: 4px 12px; + border: 1px solid ${(p) => (p.$saving ? '#aaa' : '#0084c4')}; + border-radius: 4px; + background: ${(p) => (p.$saving ? '#fafafa' : '#0084c4')}; + color: ${(p) => (p.$saving ? '#666' : '#fff')}; + cursor: ${(p) => (p.$saving ? 'default' : 'pointer')}; + font-size: 12px; + + &:hover:not(:disabled) { + background: ${(p) => (p.$saving ? '#fafafa' : '#006aa3')}; + border-color: ${(p) => (p.$saving ? '#aaa' : '#006aa3')}; + } +` + +export const StatusText = styled.span` + font-size: 12px; + color: #6f6f6f; + margin-left: auto; +` + +export const EmptyMessage = styled.div` + padding: 24px; + text-align: center; + color: #6f6f6f; + font-size: 13px; +` diff --git a/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.tsx b/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.tsx new file mode 100644 index 000000000..c92d1fd3c --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/LiveTablePlugin.tsx @@ -0,0 +1,259 @@ +import { + type IUIPlugin, + Loading, + useDocument, +} from '@development-framework/dm-core' +import { useCallback, useEffect, useState } from 'react' +import * as S from './LiveTablePlugin.styles' + +export interface LiveTablePluginConfig { + caption?: string + /** Whether viewers are allowed to add rows. Defaults to true. */ + allowAddRows?: boolean + /** Whether viewers are allowed to delete rows. Defaults to true. */ + allowDeleteRows?: boolean +} + +type TDocument = Record & { rows?: string[][] } + +const EMPTY_ROW = (colCount: number): string[] => Array(colCount).fill('') + +/** + * A live, editable table bound to a DMSS entity. + * + * The entity must have a `rows` attribute (`string[][]`): the first row is the + * header and the rest are data rows. Viewers can add/delete rows and edit cells + * in-place; clicking **Save** writes the updated rows back to the entity via + * `updateDocument`. + * + * Wire it up by binding the widget's **Data scope** in the inspector to an + * entity that contains a `rows` field. + */ +export const LiveTablePlugin = ( + props: Omit & { config: LiveTablePluginConfig } +): React.ReactElement => { + const { allowAddRows = true, allowDeleteRows = true, caption } = props.config + + const { document, isLoading, updateDocument, error } = useDocument( + props.idReference, + 1 + ) + + const [localRows, setLocalRows] = useState([]) + const [dirty, setDirty] = useState(false) + const [saving, setSaving] = useState(false) + const [selectedColumn, setSelectedColumn] = useState(null) + const [selectedRow, setSelectedRow] = useState(null) + + // Sync from DMSS whenever the document loads or changes externally. + useEffect(() => { + if (document) { + setLocalRows(document.rows ?? []) + setDirty(false) + } + }, [document]) + + const colCount = Math.max((localRows[0] ?? []).length, 1) + const header = localRows[0] ?? [] + const bodyRows = localRows.slice(1) + + const updateRows = useCallback((next: string[][]) => { + setLocalRows(next) + setDirty(true) + }, []) + + const setCellValue = (rowIndex: number, colIndex: number, value: string) => { + const next = localRows.map((row) => [...row]) + if (!next[rowIndex]) next[rowIndex] = EMPTY_ROW(colCount) + next[rowIndex][colIndex] = value + updateRows(next) + } + + const setHeaderValue = (colIndex: number, value: string) => { + const next = localRows.map((row) => [...row]) + if (!next[0]) next[0] = EMPTY_ROW(colCount) + next[0][colIndex] = value + updateRows(next) + } + + const addRow = () => { + const next = + localRows.length === 0 + ? [EMPTY_ROW(1), EMPTY_ROW(1)] + : [...localRows, EMPTY_ROW(colCount)] + updateRows(next) + } + + const deleteRow = (bodyIndex: number) => { + const next = [header, ...bodyRows.filter((_, i) => i !== bodyIndex)] + updateRows(next) + setSelectedRow(null) + } + + const addColumn = () => { + const next = localRows.map((row) => [...row, '']) + updateRows(next) + } + + const deleteColumn = (colIndex: number) => { + const next = localRows.map((row) => row.filter((_, i) => i !== colIndex)) + updateRows(next[0]?.length === 0 ? [] : next) + setSelectedColumn(null) + } + + const handleSave = async () => { + if (!document || saving) return + setSaving(true) + try { + await updateDocument({ ...document, rows: localRows }, false, false) + setDirty(false) + if (props.onSubmit) props.onSubmit({ rows: localRows }) + } finally { + setSaving(false) + } + } + + if (isLoading) return + if (error) + return ( + + Could not load table data: {String(error)} + + ) + + if (!props.idReference) + return ( + + Bind this widget to a DMSS entity via the Data scope in the inspector. + + ) + + return ( + + + {allowAddRows && ( + <> + + + Add row + + deleteRow(selectedRow!)} + disabled={saving || selectedRow === null} + > + - Delete row + + + + Add column + + deleteColumn(selectedColumn!)} + disabled={saving || selectedColumn === null} + > + - Delete column + + > + )} + + {saving ? 'Saving…' : 'Save'} + + {dirty && !saving && Unsaved changes} + {!dirty && !saving && All changes saved} + + + {localRows.length === 0 ? ( + + No data yet.{allowAddRows ? ' Click "+ Add row" to start.' : ''} + + ) : ( + + + {caption && ( + + {caption} + + )} + + {allowDeleteRows && ( + + {header.map((_, colIndex) => ( + + + colIndex === selectedColumn + ? setSelectedColumn(null) + : setSelectedColumn(colIndex) + } + disabled={saving} + style={{ + color: selectedColumn === colIndex ? 'red' : 'grey', + }} + > + ✕ + + + ))} + + + )} + + {header.map((col, colIndex) => ( + + setHeaderValue(colIndex, e.target.value)} + /> + + ))} + {allowDeleteRows && } + + + + {bodyRows.map((row, rowIndex) => ( + + {Array.from({ length: colCount }).map((_, colIndex) => ( + + + setCellValue(rowIndex + 1, colIndex, e.target.value) + } + /> + + ))} + {allowDeleteRows && ( + + + rowIndex === selectedRow + ? setSelectedRow(null) + : setSelectedRow(rowIndex) + } + disabled={saving} + style={{ + color: selectedRow === rowIndex ? 'red' : 'grey', + }} + > + ✕ + + + )} + + ))} + + + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.styles.ts b/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.styles.ts new file mode 100644 index 000000000..3db05a2c9 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.styles.ts @@ -0,0 +1,54 @@ +import styled from 'styled-components' + +export const ChartContainer = styled.div` + width: 100%; + aspect-ratio: 16 / 9; + display: flex; + flex-direction: column; + box-sizing: border-box; + min-height: 0; +` + +export const ChartTitle = styled.div` + font-size: 15px; + font-weight: 600; + margin-bottom: 4px; +` + +export const EmptyMessage = styled.div` + flex: 1; + display: flex; + align-items: center; + justify-content: center; + color: #6f6f6f; + text-align: center; +` + +export const Svg = styled.svg` + display: block; + flex: 1; + min-height: 0; +` + +export const Legend = styled.div` + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: center; + margin-top: 4px; +` + +export const LegendItem = styled.span` + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + color: #3d3d3d; +` + +export const LegendSwatch = styled.span<{ $color: string }>` + width: 10px; + height: 10px; + border-radius: 2px; + background-color: ${(props) => props.$color}; +` diff --git a/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.tsx b/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.tsx new file mode 100644 index 000000000..59c0a6a20 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/StaticChartPlugin.tsx @@ -0,0 +1,262 @@ +import type { IUIPlugin } from '@development-framework/dm-core' +import { useEffect, useMemo, useRef, useState } from 'react' +import { max as arrayMax, min as arrayMin } from '../utils/mathUtils' +import * as S from './StaticChartPlugin.styles' + +/** + * A self-contained, dependency-free chart rendered as inline SVG. Data is + * authored in the inspector as a small table (the same `table-source` field the + * Table widget uses): the first row is the header, the first column holds the + * x-axis labels and every other column is a numeric series. + * + * Mirrors the static-widget philosophy: no DMSS binding and no heavyweight + * charting dependency, so authored pages render WYSIWYG in preview and when + * published. + */ + +export interface StaticChartPluginConfig { + chartType?: 'line' | 'bar' + /** First row = header, first column = x labels, other columns = series. */ + rows?: string[][] + title?: string + showLegend?: boolean + /** Base color; multi-series charts cycle through a palette from here. */ + color?: string +} + +const PALETTE = [ + '#0084c4', + '#eb0000', + '#4bb748', + '#ff9200', + '#7d0023', + '#52c0ff', +] + +interface ChartData { + xLabels: string[] + series: { name: string; values: number[] }[] + yMin: number + yMax: number +} + +const parseChartData = (rows: string[][]): ChartData => { + const header = rows[0] ?? [] + const body = rows.slice(1) + const seriesNames = header.slice(1) + const xLabels = body.map((row) => row[0] ?? '') + + const series = seriesNames.map((name, seriesIndex) => ({ + name: name || `Series ${seriesIndex + 1}`, + values: body.map((row) => { + const value = Number(row[seriesIndex + 1]) + return Number.isFinite(value) ? value : 0 + }), + })) + + const allValues = series.flatMap((entry) => entry.values) + const rawMin = Math.min(0, arrayMin(allValues)) + const rawMax = arrayMax(allValues) + const yMax = rawMax === rawMin ? rawMin + 1 : rawMax + + return { xLabels, series, yMin: rawMin, yMax } +} + +const useElementWidth = (): [ + React.RefObject, + { width: number; height: number }, +] => { + const ref = useRef(null) + const [size, setSize] = useState({ width: 0, height: 0 }) + + useEffect(() => { + const element = ref.current + if (!element) return + const observer = new ResizeObserver((entries) => { + const entry = entries[0] + if (entry) + setSize({ + width: entry.contentRect.width, + height: entry.contentRect.height, + }) + }) + observer.observe(element) + return () => observer.disconnect() + }, []) + + return [ref, size] +} + +const niceTicks = (min: number, max: number, count = 4): number[] => { + const step = (max - min) / count + return Array.from({ length: count + 1 }, (_, index) => min + step * index) +} + +/** Render a line or bar chart from inline tabular config. */ +export const StaticChartPlugin = ( + props: Omit & { config: StaticChartPluginConfig } +): React.ReactElement => { + const { + chartType = 'line', + rows = [], + title, + showLegend = true, + color = PALETTE[0], + } = props.config + + const [ref, size] = useElementWidth() + const data = useMemo(() => parseChartData(rows), [rows]) + + const colors = useMemo(() => { + const base = [color, ...PALETTE.filter((entry) => entry !== color)] + return data.series.map((_, index) => base[index % base.length]) + }, [color, data.series]) + + const hasData = data.series.length > 0 && data.xLabels.length > 0 + + const legendHeight = showLegend && data.series.length > 0 ? 24 : 0 + const titleHeight = title ? 24 : 0 + const width = Math.max(size.width, 120) + const height = Math.max(size.height - titleHeight - legendHeight, 200) + + const margin = { top: 8, right: 12, bottom: 28, left: 40 } + const plotWidth = Math.max(width - margin.left - margin.right, 10) + const plotHeight = Math.max(height - margin.top - margin.bottom, 10) + + const xCount = data.xLabels.length + const yToPx = (value: number): number => { + const ratio = (value - data.yMin) / (data.yMax - data.yMin || 1) + return margin.top + plotHeight - ratio * plotHeight + } + const xToPx = (index: number): number => { + if (xCount <= 1) return margin.left + plotWidth / 2 + return margin.left + (index / (xCount - 1)) * plotWidth + } + + const ticks = niceTicks(data.yMin, data.yMax) + return ( + + {title ? {title} : null} + + {!size ? null : !hasData ? ( + + Add data in the inspector: a header row, x labels in the first column + and numbers in the rest. + + ) : ( + <> + + {title || 'Chart'} + + {ticks.map((tick) => { + const y = yToPx(tick) + return ( + + + + {Number(tick.toFixed(2))} + + + ) + })} + + {data.xLabels.map((label, index) => ( + + {label} + + ))} + + + + {chartType === 'line' + ? data.series.map((entry, seriesIndex) => ( + `${xToPx(index)},${yToPx(value)}`) + .join(' ')} + /> + )) + : data.xLabels.map((_, xIndex) => { + const groupWidth = plotWidth / Math.max(xCount, 1) + const barAreaWidth = groupWidth * 0.7 + const barWidth = + barAreaWidth / Math.max(data.series.length, 1) + const groupStart = + margin.left + + groupWidth * xIndex + + (groupWidth - barAreaWidth) / 2 + const baseline = yToPx(Math.max(0, data.yMin)) + return ( + + {data.series.map((entry, seriesIndex) => { + const value = entry.values[xIndex] ?? 0 + const y = yToPx(value) + const barTop = Math.min(y, baseline) + const barHeight = Math.abs(baseline - y) + return ( + + ) + })} + + ) + })} + + + {showLegend && data.series.length > 0 ? ( + + {data.series.map((entry, seriesIndex) => ( + + + {entry.name} + + ))} + + ) : null} + > + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.styles.ts b/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.styles.ts new file mode 100644 index 000000000..3b5b22f5b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.styles.ts @@ -0,0 +1,22 @@ +import { Table } from '@equinor/eds-core-react' +import styled from 'styled-components' + +export const EmptyMessage = styled.div` + padding: 16px; + color: #6f6f6f; + text-align: center; +` + +export const TableContainer = styled.div` + width: 100%; +` + +export const FullWidthTable = styled(Table)` + width: 100%; +` + +export const PaginationContainer = styled.div` + display: flex; + justify-content: center; + margin-top: 8px; +` diff --git a/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.tsx b/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.tsx new file mode 100644 index 000000000..9c79a3ff1 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/StaticTablePlugin.tsx @@ -0,0 +1,75 @@ +import type { IUIPlugin } from '@development-framework/dm-core' +import { Pagination, Table } from '@equinor/eds-core-react' +import { useMemo, useState } from 'react' +import * as S from './StaticTablePlugin.styles' + +export interface StaticTablePluginConfig { + /** Full grid of cells; the first row is the header. */ + rows?: string[][] + /** Rows shown per page; defaults to 25. */ + pageSize?: number + caption?: string +} + +/** + * Renders a self-contained table from inline config. The first row is the + * header. Large tables are paginated so the page stays responsive regardless of + * row count. No DMSS binding needed. + */ +export const StaticTablePlugin = ( + props: Omit & { config: StaticTablePluginConfig } +): React.ReactElement => { + const { rows = [], pageSize = 25, caption } = props.config + const [page, setPage] = useState(1) + + const [columns, body] = useMemo(() => [rows[0] ?? [], rows.slice(1)], [rows]) + const totalPages = Math.max(1, Math.ceil(body.length / pageSize)) + const safePage = Math.min(page, totalPages) + const pageRows = useMemo( + () => body.slice((safePage - 1) * pageSize, safePage * pageSize), + [body, safePage, pageSize] + ) + + if (columns.length === 0 && body.length === 0) + return ( + + No data. Write a table or upload a CSV/Excel file in the inspector. + + ) + + return ( + + + {caption && {caption}} + + + {columns.map((label, index) => ( + {label} + ))} + + + + {pageRows.map((row, rowIndex) => ( + + {columns.map((_, colIndex) => ( + + {row[colIndex] ?? ''} + + ))} + + ))} + + + {body.length > pageSize && ( + + setPage(newPage)} + withItemIndicator + /> + + )} + + ) +} diff --git a/packages/dm-core-plugins/src/builder/static/staticWidgets.styles.ts b/packages/dm-core-plugins/src/builder/static/staticWidgets.styles.ts new file mode 100644 index 000000000..f0657586c --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/staticWidgets.styles.ts @@ -0,0 +1,112 @@ +import { Button } from '@equinor/eds-core-react' +import type { CSSProperties } from 'react' +import styled from 'styled-components' + +export const FullWidth = styled.div` + width: 100%; +` + +export const Heading = styled.h2<{ + $align: 'left' | 'center' | 'right' + $color?: string + $fontSize: string +}>` + margin: 0; + text-align: ${(props) => props.$align}; + color: ${(props) => props.$color}; + font-size: ${(props) => props.$fontSize}; + font-weight: 600; + line-height: 1.2; +` + +export const ButtonContainer = styled.div<{ + $justifyContent: CSSProperties['justifyContent'] +}>` + display: flex; + align-items: center; + justify-content: ${(props) => props.$justifyContent}; + width: 100%; + height: 100%; +` + +export const OverflowHiddenButton = styled(Button)` + overflow: hidden; +` + +export const DividerContainer = styled.div<{ $spacing: number }>` + display: flex; + align-items: center; + width: 100%; + height: 100%; + padding: ${(props) => props.$spacing}px 0; + box-sizing: border-box; +` + +export const DividerLine = styled.hr<{ $color: string; $thickness: number }>` + width: 100%; + border: none; + border-top: ${(props) => props.$thickness}px solid ${(props) => props.$color}; + margin: 0; +` + +export const Spacer = styled.div<{ $height: number }>` + width: 100%; + height: ${(props) => props.$height}px; +` + +export const EmptyMessage = styled.div` + padding: 16px; + color: #6f6f6f; + text-align: center; +` + +export const EmbedFrame = styled.div` + position: relative; + width: 100%; + height: 100%; + min-height: 0; + aspect-ratio: 16 / 9; +` + +export const EmbedIframe = styled.iframe` + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: none; + border-radius: 4px; +` + +export const MetricContainer = styled.div<{ + $alignItems: CSSProperties['alignItems'] +}>` + display: flex; + flex-direction: column; + justify-content: center; + align-items: ${(props) => props.$alignItems}; + width: 100%; + height: 100%; + box-sizing: border-box; + overflow: hidden; +` + +export const MetricLabel = styled.div` + font-size: 13px; + font-weight: 500; + color: #6f6f6f; + text-transform: uppercase; + letter-spacing: 0.4px; +` + +export const MetricValue = styled.div<{ $color: string }>` + font-size: 40px; + font-weight: 700; + line-height: 1.1; + color: ${(props) => props.$color}; +` + +export const MetricUnit = styled.span` + font-size: 20px; + font-weight: 500; + margin-left: 4px; +` diff --git a/packages/dm-core-plugins/src/builder/static/staticWidgets.tsx b/packages/dm-core-plugins/src/builder/static/staticWidgets.tsx new file mode 100644 index 000000000..88ec10886 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/static/staticWidgets.tsx @@ -0,0 +1,254 @@ +import type { IUIPlugin } from '@development-framework/dm-core' +import { createElement } from 'react' +import { + aggregate, + formatNumber, + parseNumbers, + type TAggregation, +} from '../utils/mathUtils' +import * as S from './staticWidgets.styles' + +/** + * Self-contained "static" widgets for the website builder. Each renders purely + * from its inline recipe config — no DMSS entity binding required — so authored + * pages render WYSIWYG in both the builder preview and when published. + * + * These mirror the StaticTablePlugin pattern: a plain component reading typed + * config, registered in `src/index.tsx` and surfaced as a palette block in + * `builder/blocks.ts`. + */ + +type TAlign = 'left' | 'center' | 'right' + +const alignToFlex = ( + align: TAlign | undefined +): React.CSSProperties['justifyContent'] => + align === 'center' ? 'center' : align === 'right' ? 'flex-end' : 'flex-start' + +// ---- Heading -------------------------------------------------------------- + +export interface StaticHeadingPluginConfig { + text?: string + /** 1–6; drives the rendered heading tag and default size. */ + level?: number + align?: TAlign + color?: string +} + +const HEADING_SIZES: Record = { + 1: '40px', + 2: '32px', + 3: '26px', + 4: '21px', + 5: '17px', + 6: '14px', +} + +/** A page heading/title rendered from inline config. */ +export const StaticHeadingPlugin = ( + props: Omit & { config: StaticHeadingPluginConfig } +): React.ReactElement => { + const { text = 'Heading', level = 2, align = 'left', color } = props.config + const safeLevel = Math.min(6, Math.max(1, Math.round(level))) + + return ( + + {createElement( + S.Heading, + { + as: `h${safeLevel}`, + $align: align, + $color: color, + $fontSize: HEADING_SIZES[safeLevel], + }, + text + )} + + ) +} + +// ---- Button --------------------------------------------------------------- + +export interface StaticButtonPluginConfig { + label?: string + href?: string + variant?: 'contained' | 'outlined' | 'ghost' + align?: TAlign + openInNewTab?: boolean +} + +/** A call-to-action button/link rendered from inline config. */ +export const StaticButtonPlugin = ( + props: Omit & { config: StaticButtonPluginConfig } +): React.ReactElement => { + const { + label = 'Button', + href = '', + variant = 'contained', + align = 'left', + openInNewTab = false, + } = props.config + + return ( + + + {label} + + + ) +} + +// ---- Divider -------------------------------------------------------------- + +export interface StaticDividerPluginConfig { + color?: string + /** Line thickness in pixels. */ + thickness?: number + /** Vertical space above and below the line, in pixels. */ + spacing?: number +} + +/** A horizontal rule to separate sections. */ +export const StaticDividerPlugin = ( + props: Omit & { config: StaticDividerPluginConfig } +): React.ReactElement => { + const { color = '#d8d8d8', thickness = 1, spacing = 8 } = props.config + return ( + + + + ) +} + +// ---- Spacer --------------------------------------------------------------- + +export interface StaticSpacerPluginConfig { + /** Height of the empty space, in pixels. */ + height?: number +} + +/** Empty vertical whitespace for spacing out content. */ +export const StaticSpacerPlugin = ( + props: Omit & { config: StaticSpacerPluginConfig } +): React.ReactElement => { + const { height = 24 } = props.config + return +} + +// ---- Video / Embed -------------------------------------------------------- + +export interface StaticEmbedPluginConfig { + url?: string + title?: string +} + +/** + * Convert common share URLs (YouTube, Vimeo) into their embeddable form. Any + * other URL is embedded as-is, so generic iframes still work. + */ +const toEmbedUrl = (url: string): string => { + try { + const parsed = new URL(url) + const host = parsed.hostname.replace(/^www\./, '') + if (host === 'youtube.com' || host === 'm.youtube.com') { + const id = parsed.searchParams.get('v') + if (id) return `https://www.youtube.com/embed/${id}` + } + if (host === 'youtu.be') { + const id = parsed.pathname.slice(1) + if (id) return `https://www.youtube.com/embed/${id}` + } + if (host === 'vimeo.com') { + const id = parsed.pathname.split('/').filter(Boolean)[0] + if (id) return `https://player.vimeo.com/video/${id}` + } + return url + } catch { + return url + } +} + +/** Embed a video or other external page via a responsive 16:9 iframe. */ +export const StaticEmbedPlugin = ( + props: Omit & { config: StaticEmbedPluginConfig } +): React.ReactElement => { + const { url = '', title = 'Embedded content' } = props.config + + if (!url) + return ( + + Paste a video or page URL in the inspector to embed it. + + ) + + return ( + + + + ) +} + +// ---- Metric / KPI --------------------------------------------------------- + +export interface StaticMetricPluginConfig { + label?: string + /** Free-form text; all numbers found are extracted and aggregated. */ + values?: string + aggregation?: TAggregation + unit?: string + decimals?: number + color?: string + align?: TAlign +} + +/** + * A single big-number KPI. Reads numbers out of `values`, reduces them with the + * chosen aggregation (mean, sum, min, max, …) and renders the result with an + * optional label and unit. Self-contained — no DMSS binding required. + */ +export const StaticMetricPlugin = ( + props: Omit & { config: StaticMetricPluginConfig } +): React.ReactElement => { + const { + label = 'Metric', + values = '', + aggregation = 'mean', + unit = '', + decimals = 2, + color = '#243746', + align = 'left', + } = props.config + + const numbers = parseNumbers(values) + const result = aggregate(numbers, aggregation) + const display = numbers.length === 0 ? '–' : formatNumber(result, decimals) + + return ( + + {label} + + {display} + {unit ? {unit} : null} + + + ) +} diff --git a/packages/dm-core-plugins/src/builder/styles/directory.styles.ts b/packages/dm-core-plugins/src/builder/styles/directory.styles.ts new file mode 100644 index 000000000..b50e1e529 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/styles/directory.styles.ts @@ -0,0 +1,188 @@ +import { Button, Dialog, Typography } from '@equinor/eds-core-react' +import { tokens } from '@equinor/eds-tokens' +import styled from 'styled-components' + +// EDS token aliases used by the Site directory and its cards. +const { + interactive: { + danger__resting: { hex: colorDangerResting }, + }, + text: { + static_icons__tertiary: { hex: colorTextTertiary }, + static_icons__primary_white: { hex: colorTextWhite }, + }, + ui: { + background__default: { hex: colorBgDefault }, + background__medium: { hex: colorBgMedium }, + }, +} = tokens.colors + +// ---- Directory layout --------------------------------------------------- + +export const Root = styled.div` + width: 100%; +` + +export const HeaderRow = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 24px; +` + +export const HeaderActions = styled.div` + display: flex; + align-items: center; + gap: 8px; +` + +export const DialogActions = styled(Dialog.Actions)` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +` + +export const AlertRow = styled.div` + display: flex; + align-items: center; + gap: 8px; +` + +export const EmptyState = styled.div` + text-align: center; + padding: 48px 0; +` + +export const CardGrid = styled.div` + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 20px; +` + +/** Muted helper/caption text (loading, empty and info messages). */ +export const MutedText = styled(Typography)` + color: ${colorTextTertiary}; +` + +/** Danger message shown inside the delete dialog. */ +export const DialogErrorText = styled(Typography)` + color: ${colorDangerResting}; + margin-top: 8px; +` + +/** Create-error banner above the gallery. */ +export const CreateErrorText = styled(Typography)` + margin-bottom: 12px; +` + +export const EmptyTitle = styled(Typography)` + margin-bottom: 8px; + color: ${colorTextTertiary}; +` + +// ---- Site card ---------------------------------------------------------- + +export const Card = styled.div<{ $deleting: boolean }>` + border-radius: 12px; + background: ${colorBgDefault}; + border: 1px solid ${colorBgMedium}; + overflow: hidden; + display: flex; + flex-direction: column; + transition: + box-shadow 0.15s ease, + transform 0.15s ease; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.07); + opacity: ${(props) => (props.$deleting ? 0.5 : 1)}; + + &:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.14); + transform: translateY(-2px); + } +` + +/** Coloured banner; hue is derived per-site so each card looks distinct. */ +export const Banner = styled.div<{ $hue: number }>` + background: linear-gradient( + 135deg, + hsl(${(props) => props.$hue}, 55%, 38%) 0%, + hsl(${(props) => (props.$hue + 40) % 360}, 60%, 52%) 100% + ); + height: 100px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + position: relative; + overflow: hidden; +` + +export const BannerInitial = styled.span` + font-size: 52px; + font-weight: 700; + color: rgba(255, 255, 255, 0.8); + line-height: 1; + user-select: none; + letter-spacing: -2px; +` + +export const DraftBadge = styled.span` + position: absolute; + top: 10px; + right: 10px; + padding: 3px 10px; + border-radius: 12px; + background: rgba(0, 0, 0, 0.4); + color: ${colorTextWhite}; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.6px; + text-transform: uppercase; +` + +export const CardBody = styled.div` + padding: 14px 16px 16px; + display: flex; + flex-direction: column; + gap: 2px; + flex-grow: 1; +` + +export const CardTitle = styled(Typography)` + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 16px; +` + +export const CardCaption = styled(Typography)` + color: ${colorTextTertiary}; + margin-bottom: 14px; +` + +export const ActionRow = styled.div` + display: flex; + gap: 8px; + margin-top: auto; +` + +/** Link wrapping the primary "Open" button; grows to fill the row. */ +export const OpenLink = styled.a` + text-decoration: none; + flex-grow: 1; +` + +export const EditLink = styled.a` + text-decoration: none; +` + +export const FullWidthButton = styled(Button)` + width: 100%; +` + +export const DangerIconButton = styled(Button)` + color: ${colorDangerResting}; +` diff --git a/packages/dm-core-plugins/src/builder/styles/index.ts b/packages/dm-core-plugins/src/builder/styles/index.ts new file mode 100644 index 000000000..a185604ca --- /dev/null +++ b/packages/dm-core-plugins/src/builder/styles/index.ts @@ -0,0 +1,806 @@ +import { tokens } from '@equinor/eds-tokens' +import styled, { css } from 'styled-components' +import type { TGridSize } from '../../grid' + +// Aliases for EDS tokens used throughout this file. +const { + interactive: { + primary__resting: { hex: colorPrimary }, + primary__hover_alt: { hex: colorPrimaryHoverAlt }, + danger__hover: { hex: colorDangerHover }, + danger__resting: { hex: colorDangerResting }, + }, + text: { + static_icons__default: { hex: colorTextDefault }, + static_icons__tertiary: { hex: colorTextTertiary }, + }, + ui: { + background__default: { hex: colorBgDefault }, + background__light: { hex: colorBgLight }, + background__medium: { hex: colorBgMedium }, + background__warning: { hex: colorBgWarning }, + background__danger: { hex: colorBgDanger }, + }, +} = tokens.colors + +export const BuilderLayout = styled.div` + display: grid; + grid-template-columns: 220px 1fr 300px; + grid-template-rows: auto 1fr; + grid-template-areas: + 'toolbar toolbar toolbar' + 'palette canvas inspector'; + height: 100%; + min-height: 480px; + gap: 0; +` + +export const Toolbar = styled.div` + grid-area: toolbar; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 8px 12px; + border-bottom: 1px solid ${colorBgMedium}; + background: ${colorBgLight}; +` + +export const ToolbarGroup = styled.div` + display: flex; + align-items: center; + gap: 8px; +` + +export const SaveStatus = styled.span<{ $state: 'saved' | 'saving' | 'dirty' }>` + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: ${(props) => + props.$state === 'dirty' ? colorDangerHover : colorTextTertiary}; + + &::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: ${(props) => + props.$state === 'saved' + ? '#4caf50' + : props.$state === 'saving' + ? colorBgMedium + : '#ffa000'}; + } +` + +export const LeftPanel = styled.div` + grid-area: palette; + border-right: 1px solid ${colorBgMedium}; + background: ${colorBgDefault}; + display: flex; + flex-direction: column; + overflow: hidden; +` + +export const PalettePanel = styled.div` + flex: 1 1 auto; + min-height: 0; + padding: 12px; + overflow-y: auto; + background: ${colorBgDefault}; +` + +export const OutlinePanel = styled.div` + flex: 0 0 auto; + max-height: 40%; + padding: 12px; + border-top: 1px solid ${colorBgMedium}; + overflow-y: auto; + background: ${colorBgLight}; +` + +export const OutlineList = styled.div` + display: flex; + flex-direction: column; + gap: 2px; + margin-top: 6px; +` + +export const OutlineRow = styled.div<{ $selected: boolean }>` + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 4px 6px; + border: 1px solid + ${(props) => (props.$selected ? colorPrimary : 'transparent')}; + border-radius: 4px; + background: ${(props) => + props.$selected ? colorPrimaryHoverAlt : 'transparent'}; + cursor: pointer; + font-size: 13px; + text-align: left; + + &:hover { + background: ${colorPrimaryHoverAlt}; + } +` + +export const OutlineLabel = styled.span` + flex: 1 1 auto; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +` + +export const OutlineCount = styled.span` + flex: 0 0 auto; + color: ${colorTextTertiary}; + font-size: 11px; +` + +export const OutlineEmpty = styled.div` + color: ${colorTextTertiary}; + font-size: 12px; + padding: 6px 0; +` + +export const PaletteGroupTitle = styled.div` + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: ${colorTextTertiary}; + margin: 12px 0 6px; +` + +export const PaletteCard = styled.button` + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 8px; + margin-bottom: 6px; + border: 1px solid ${colorBgMedium}; + border-radius: 6px; + background: ${colorBgDefault}; + cursor: grab; + text-align: left; + font-size: 13px; + + &:hover { + border-color: ${colorPrimary}; + background: ${colorPrimaryHoverAlt}; + } + + &:active { + cursor: grabbing; + } +` + +export const CanvasPanel = styled.div` + grid-area: canvas; + padding: 16px; + overflow: auto; + background: ${colorBgLight}; +` + +export const FullBleedCanvasPanel = styled(CanvasPanel)` + grid-column: 1 / -1; +` + +export const JsonPanel = styled(CanvasPanel)` + grid-column: 1 / -1; +` + +export const JsonPre = styled.pre` + margin: 0; + white-space: pre-wrap; + word-break: break-word; + font-size: 12px; +` + +export const DeviceFrame = styled.div<{ $maxWidth: string; $framed?: boolean }>` + width: 100%; + max-width: ${(props) => props.$maxWidth}; + margin: 0 auto; + height: 100%; + transition: max-width 0.2s ease; + + /* + * When emulating a tablet/phone, draw a device bezel so the screen edges are + * obvious. The frame sizes to its content, is capped to the panel height and + * scrolls internally, so it reads like a device screen rather than a narrowed + * column. Desktop keeps the plain full-width layout (no bezel). + */ + ${(props) => + props.$framed && + css` + height: auto; + max-height: 100%; + overflow: auto; + background: ${colorBgDefault}; + border: 10px solid #1c1c1c; + border-radius: 28px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.28); + `} +` + +export const Breadcrumbs = styled.div` + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 2px; + margin-bottom: 8px; + font-size: 13px; + color: ${colorTextTertiary}; +` + +export const BreadcrumbButton = styled.button<{ $current: boolean }>` + border: none; + background: none; + padding: 2px 4px; + cursor: ${(props) => (props.$current ? 'default' : 'pointer')}; + color: ${(props) => (props.$current ? colorTextDefault : colorPrimary)}; + font-weight: ${(props) => (props.$current ? 600 : 400)}; + font-size: 13px; + + &:hover { + text-decoration: ${(props) => (props.$current ? 'none' : 'underline')}; + } +` + +export const CanvasGrid = styled.div<{ $size: TGridSize; $editing: boolean }>` + display: grid; + grid-template-columns: repeat(${(props) => props.$size.columns}, 1fr); + grid-template-rows: repeat(${(props) => props.$size.rows}, minmax(24px, 1fr)); + row-gap: ${(props) => props.$size.rowGap}; + column-gap: ${(props) => props.$size.columnGap}; + min-height: 100%; + background-color: ${colorBgDefault}; + background-image: ${(props) => + props.$editing + ? `linear-gradient(${colorBgMedium} 1px, transparent 1px), linear-gradient(90deg, ${colorBgMedium} 1px, transparent 1px)` + : 'none'}; + background-size: ${(props) => + `calc(100% / ${props.$size.columns}) calc(100% / ${props.$size.rows})`}; + border: 1px solid ${colorBgMedium}; + border-radius: 6px; + padding: 4px; +` + +export const CanvasItem = styled.div<{ + $selected: boolean + $dragging?: boolean +}>` + position: relative; + overflow: hidden; + border: 2px solid + ${(props) => (props.$selected ? colorPrimary : colorBgMedium)}; + border-radius: 6px; + background: ${colorBgDefault}; + box-shadow: ${(props) => + props.$selected ? `0 0 0 2px ${colorPrimaryHoverAlt}` : 'none'}; + opacity: ${(props) => (props.$dragging ? 0.7 : 1)}; + z-index: ${(props) => (props.$dragging || props.$selected ? 2 : 1)}; +` + +export const CanvasItemHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; + padding: 4px 6px; + background: ${colorBgLight}; + border-bottom: 1px solid ${colorBgMedium}; + font-size: 12px; + font-weight: 600; + cursor: grab; + + &:active { + cursor: grabbing; + } +` + +export const CanvasItemActions = styled.div` + display: flex; + align-items: center; + gap: 0; +` + +export const ResizeHandle = styled.div` + position: absolute; + right: 0; + bottom: 0; + width: 14px; + height: 14px; + cursor: nwse-resize; + background: linear-gradient( + 135deg, + transparent 0 50%, + ${colorPrimary} 50% 60%, + transparent 60% 70%, + ${colorPrimary} 70% 80%, + transparent 80% + ); + touch-action: none; +` + +export const CanvasItemBody = styled.div` + display: flex; + align-items: center; + justify-content: center; + padding: 8px; + color: ${colorTextTertiary}; + font-size: 12px; + height: 100%; +` + +export const EmptyState = styled.div` + grid-column: 1 / -1; + grid-row: 1 / -1; + display: flex; + align-items: center; + justify-content: center; + color: ${colorTextTertiary}; + font-size: 14px; + text-align: center; +` + +export const InspectorPanel = styled.div` + grid-area: inspector; + border-left: 1px solid ${colorBgMedium}; + padding: 12px; + overflow-y: auto; + background: ${colorBgDefault}; +` + +export const InspectorEmpty = styled.div` + color: ${colorTextTertiary}; + font-size: 13px; + padding: 8px 0; +` + +export const InspectorSection = styled.div` + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid ${colorBgMedium}; + + &:last-child { + border-bottom: none; + } +` + +export const InspectorSectionTitle = styled.div` + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: ${colorTextTertiary}; + margin-bottom: 8px; +` + +export const InspectorField = styled.div` + margin-bottom: 10px; +` + +export const LayoutGrid = styled.div` + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +` + +export const FieldHelp = styled.div` + font-size: 11px; + color: ${colorTextTertiary}; + margin-top: 2px; +` + +export const Textarea = styled.textarea` + width: 100%; + min-height: 96px; + resize: vertical; + padding: 8px; + font-family: inherit; + font-size: 13px; + border: 1px solid ${colorBgMedium}; + border-radius: 0; + box-sizing: border-box; + + &:focus { + outline: 2px solid ${colorPrimary}; + outline-offset: -2px; + } +` + +export const FieldLabel = styled.label` + display: block; + font-size: 12px; + font-weight: 500; + color: ${colorTextDefault}; + margin-bottom: 4px; +` + +/* A small two-option segmented toggle used in inspector fields. */ +export const SegmentedControl = styled.div` + display: flex; + gap: 0; + margin-bottom: 8px; +` + +export const SegmentButton = styled.button<{ $active: boolean }>` + flex: 1 1 0; + padding: 6px 8px; + font-size: 12px; + font-weight: 500; + cursor: pointer; + border: 1px solid + ${(props) => (props.$active ? colorPrimary : colorBgMedium)}; + background: ${(props) => (props.$active ? colorPrimary : colorBgDefault)}; + color: ${(props) => + props.$active + ? tokens.colors.text.static_icons__primary_white.hex + : colorTextDefault}; + + &:first-child { + border-radius: 4px 0 0 4px; + } + + &:last-child { + border-radius: 0 4px 4px 0; + border-left: ${(props) => (props.$active ? undefined : 'none')}; + } + + &:hover { + background: ${(props) => + props.$active ? colorPrimary : colorPrimaryHoverAlt}; + } +` + +/* ------------------------------------------------------------------ * + * Website nav sidebar (the site's own left navigation, shown in both * + * edit and preview so the canvas is WYSIWYG). * + * ------------------------------------------------------------------ */ + +export const SiteFrame = styled.div` + display: flex; + align-items: stretch; + height: 100%; + min-height: 0; + gap: 0; +` + +export const NavSidebar = styled.nav<{ $editing: boolean }>` + flex: 0 0 200px; + width: 200px; + display: flex; + flex-direction: column; + background: ${colorBgLight}; + border-right: 1px solid ${colorBgMedium}; + border-radius: 6px 0 0 6px; + overflow-y: auto; + padding: 12px 8px; + box-sizing: border-box; +` + +export const NavHeader = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 4px; + padding: 0 4px 8px; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + color: ${colorTextTertiary}; +` + +export const NavList = styled.div` + display: flex; + flex-direction: column; + gap: 2px; +` + +export const NavItem = styled.div<{ $active: boolean; $dragging?: boolean }>` + display: flex; + align-items: center; + gap: 4px; + padding: 6px 8px; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + color: ${(props) => + props.$active + ? tokens.colors.text.static_icons__primary_white.hex + : colorTextDefault}; + background: ${(props) => (props.$active ? colorPrimary : 'transparent')}; + opacity: ${(props) => (props.$dragging ? 0.5 : 1)}; + + &:hover { + background: ${(props) => (props.$active ? colorPrimary : colorBgMedium)}; + } +` + +export const NavItemLabel = styled.span` + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +` + +export const NavItemActions = styled.div` + display: flex; + align-items: center; + gap: 0; + flex: 0 0 auto; +` + +export const NavDragHandle = styled.span` + display: inline-flex; + align-items: center; + cursor: grab; + color: inherit; + opacity: 0.6; + + &:active { + cursor: grabbing; + } +` + +export const NavDisclosure = styled.button<{ $expanded: boolean }>` + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 18px; + height: 18px; + padding: 0; + border: none; + background: transparent; + cursor: pointer; + color: inherit; + transform: rotate(${(props) => (props.$expanded ? 90 : 0)}deg); + transition: transform 0.15s ease; +` + +export const NavDisclosureSpacer = styled.span` + flex: 0 0 auto; + width: 18px; +` + +export const NavRenameInput = styled.input` + flex: 1; + min-width: 0; + font: inherit; + font-size: 14px; + padding: 2px 4px; + border: 1px solid ${colorPrimary}; + border-radius: 4px; + box-sizing: border-box; +` + +export const NavAddButton = styled.button` + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + margin-top: 8px; + padding: 6px 8px; + border: 1px dashed ${colorBgMedium}; + border-radius: 5px; + background: transparent; + cursor: pointer; + font-size: 13px; + color: ${colorPrimary}; + + &:hover { + background: ${colorPrimaryHoverAlt}; + border-color: ${colorPrimary}; + } +` + +export const SitePageArea = styled.div` + flex: 1 1 auto; + min-width: 0; + display: flex; + flex-direction: column; +` + +/* ------------------------------------------------------------------ * + * Customizable top navbar (a full-width header above the sidebar and * + * content, shared by every page and rendered in edit + preview). * + * ------------------------------------------------------------------ */ + +/** Column wrapper: navbar on top, then the sidebar + content row below. */ +export const SiteShell = styled.div` + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; +` + +export const Navbar = styled.header<{ $background: string; $color: string }>` + display: flex; + align-items: center; + gap: 16px; + flex: 0 0 auto; + padding: 10px 16px; + background: ${(props) => props.$background}; + color: ${(props) => props.$color}; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 6px 6px 0 0; + box-sizing: border-box; +` + +export const NavbarBrand = styled.div<{ $color: string }>` + display: flex; + align-items: center; + gap: 8px; + flex: 0 0 auto; + font-size: 18px; + font-weight: 700; + color: ${(props) => props.$color}; +` + +export const NavbarBrandInput = styled.input` + font: inherit; + font-size: 18px; + font-weight: 700; + color: inherit; + background: rgba(255, 255, 255, 0.85); + padding: 2px 6px; + border: 1px solid ${colorPrimary}; + border-radius: 4px; + box-sizing: border-box; +` + +export const NavbarItems = styled.nav<{ $align: 'left' | 'center' | 'right' }>` + display: flex; + align-items: center; + gap: 4px; + flex: 1 1 auto; + min-width: 0; + justify-content: ${(props) => + props.$align === 'left' + ? 'flex-start' + : props.$align === 'center' + ? 'center' + : 'flex-end'}; +` + +export const NavbarLink = styled.button<{ $color: string }>` + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border: none; + border-radius: 5px; + background: transparent; + cursor: pointer; + font: inherit; + font-size: 15px; + color: ${(props) => props.$color}; + + &:hover { + background: rgba(0, 0, 0, 0.06); + } +` + +export const NavbarItemEditor = styled.div<{ $dragging?: boolean }>` + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px 4px; + border: 1px solid rgba(0, 0, 0, 0.12); + border-radius: 5px; + background: rgba(255, 255, 255, 0.6); + opacity: ${(props) => (props.$dragging ? 0.5 : 1)}; +` + +export const NavbarItemInput = styled.input` + font: inherit; + font-size: 14px; + color: ${colorTextDefault}; + background: ${colorBgDefault}; + padding: 2px 4px; + border: 1px solid ${colorPrimary}; + border-radius: 4px; + box-sizing: border-box; + width: 100px; +` + +export const NavbarTargetSelect = styled.select` + font: inherit; + font-size: 13px; + color: ${colorTextDefault}; + background: ${colorBgDefault}; + padding: 2px 4px; + border: 1px solid ${colorBgMedium}; + border-radius: 4px; + box-sizing: border-box; + max-width: 140px; +` + +export const NavbarUrlInput = styled.input` + font: inherit; + font-size: 13px; + color: ${colorTextDefault}; + background: ${colorBgDefault}; + padding: 2px 4px; + border: 1px solid ${colorBgMedium}; + border-radius: 4px; + box-sizing: border-box; + width: 140px; +` + +export const NavbarAddButton = styled.button` + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + flex: 0 0 auto; + padding: 6px 10px; + border: 1px dashed rgba(0, 0, 0, 0.3); + border-radius: 5px; + background: transparent; + cursor: pointer; + font-size: 13px; + color: inherit; + + &:hover { + background: rgba(0, 0, 0, 0.06); + } +` + +export const NavbarEmptyAffordance = styled.button` + display: flex; + align-items: center; + gap: 8px; + width: 100%; + flex: 0 0 auto; + padding: 10px 16px; + border: 1px dashed ${colorBgMedium}; + border-radius: 6px 6px 0 0; + background: ${colorBgLight}; + cursor: pointer; + font-size: 14px; + color: ${colorPrimary}; + box-sizing: border-box; + + &:hover { + background: ${colorPrimaryHoverAlt}; + border-color: ${colorPrimary}; + } +` + +export const NavbarSettings = styled.div` + position: relative; + flex: 0 0 auto; +` + +export const NavbarSettingsPanel = styled.div` + position: absolute; + top: calc(100% + 6px); + right: 0; + z-index: 20; + display: flex; + flex-direction: column; + gap: 10px; + width: 220px; + padding: 12px; + background: ${colorBgDefault}; + color: ${colorTextDefault}; + border: 1px solid ${colorBgMedium}; + border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +` + +export const NavbarSettingsRow = styled.label` + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + font-size: 13px; +` diff --git a/packages/dm-core-plugins/src/builder/tests/Inspector.test.tsx b/packages/dm-core-plugins/src/builder/tests/Inspector.test.tsx new file mode 100644 index 000000000..759f75702 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/Inspector.test.tsx @@ -0,0 +1,130 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { getBlock } from '../model/blocks' +import { Inspector, type TInspectorHandlers } from '../components/Inspector' +import { addWidget, createEmptyModel } from '../model/model' + +const itemForBlock = (blockId: string) => { + const block = getBlock(blockId) + if (!block) throw new Error(`missing block ${blockId}`) + const model = addWidget(createEmptyModel(), block) + return { item: model.items[0], block } +} + +const handlers = (): TInspectorHandlers => ({ + onTitle: jest.fn(), + onLabel: jest.fn(), + onScope: jest.fn(), + onArea: jest.fn(), + onConfigValue: jest.fn(), + onStyleValue: jest.fn(), + onCommit: jest.fn(), +}) + +const gridSize = createEmptyModel().size + +describe('Inspector', () => { + it('prompts to select a widget when none is active', () => { + render( + + ) + expect( + screen.getByText('Select a widget on the canvas to edit its properties.') + ).not.toBeNull() + }) + + it('edits the title', async () => { + const user = userEvent.setup() + const h = handlers() + const { item, block } = itemForBlock('text') + render( + + ) + + await user.type(screen.getByLabelText('Title'), '!') + expect(h.onTitle).toHaveBeenCalled() + }) + + it('edits the scope / data binding', async () => { + const user = userEvent.setup() + const h = handlers() + const { item, block } = itemForBlock('text') + render( + + ) + + await user.type(screen.getByLabelText('Scope'), 'orders') + expect(h.onScope).toHaveBeenCalled() + }) + + it('routes a block config field edit to onConfigValue', async () => { + const user = userEvent.setup() + const h = handlers() + // The Text block exposes a "Content" config field (target key `content`). + const { item, block } = itemForBlock('text') + render( + + ) + + await user.type(screen.getByLabelText('Content'), 'x') + expect(h.onConfigValue).toHaveBeenCalledWith('content', expect.any(String)) + }) + + it('updates the grid area from the layout fields', () => { + const h = handlers() + const { item, block } = itemForBlock('text') + render( + + ) + + fireEvent.change(screen.getByLabelText('Width'), { target: { value: '4' } }) + expect(h.onArea).toHaveBeenCalled() + }) + + it('commits (ends the undo run) on blur', () => { + const h = handlers() + const { item, block } = itemForBlock('text') + const { container } = render( + + ) + + fireEvent.blur(screen.getByLabelText('Title'), { relatedTarget: container }) + expect(h.onCommit).toHaveBeenCalled() + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/NavSidebar.test.tsx b/packages/dm-core-plugins/src/builder/tests/NavSidebar.test.tsx new file mode 100644 index 000000000..afa36901b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/NavSidebar.test.tsx @@ -0,0 +1,155 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { NavSidebar } from '../components/NavSidebar' +import { createPage, type TBuilderPage } from '../model/site' + +/** Build a small page tree: Home, About (with a Team sub-page), Contact. */ +const buildPages = (): TBuilderPage[] => { + const home = createPage('Home') + const about = createPage('About') + const team = createPage('Team') + about.children = [team] + const contact = createPage('Contact') + return [home, about, contact] +} + +const noop = () => {} + +const baseProps = (pages: TBuilderPage[]) => ({ + pages, + activePageId: pages[0].id, + editing: true, + onNavigate: noop, + onAddPage: noop, + onRenamePage: noop, + onDeletePage: noop, + onReorder: noop, +}) + +describe('NavSidebar', () => { + it('navigates to a page when its row is clicked', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onNavigate = jest.fn() + render() + + await user.click(screen.getByText('Contact')) + + expect(onNavigate).toHaveBeenCalledWith(pages[2].id) + }) + + it('adds a top-level page from the footer button', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onAddPage = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Add page' })) + + expect(onAddPage).toHaveBeenCalledWith(null) + }) + + it('adds a sub-page under a specific page', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onAddPage = jest.fn() + render() + + await user.click( + screen.getByRole('button', { name: 'Add sub-page to About' }) + ) + + expect(onAddPage).toHaveBeenCalledWith(pages[1].id) + }) + + it('renames a page inline and commits on Enter', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onRenamePage = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Rename Home' })) + const input = screen.getByDisplayValue('Home') + await user.clear(input) + await user.type(input, 'Landing{Enter}') + + expect(onRenamePage).toHaveBeenCalledWith(pages[0].id, 'Landing') + }) + + it('does not rename when the draft is cleared to whitespace', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onRenamePage = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Rename Home' })) + const input = screen.getByDisplayValue('Home') + await user.clear(input) + await user.type(input, ' {Enter}') + + expect(onRenamePage).not.toHaveBeenCalled() + }) + + it('deletes a page', async () => { + const user = userEvent.setup() + const pages = buildPages() + const onDeletePage = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Delete Contact' })) + + expect(onDeletePage).toHaveBeenCalledWith(pages[2].id) + }) + + it('hides delete for the only remaining top-level page', () => { + const pages = [createPage('Home')] + render() + + expect( + screen.queryByRole('button', { name: 'Delete Home' }) + ).toBeNull() + }) + + it('collapses and expands a page with children', async () => { + const user = userEvent.setup() + const pages = buildPages() + render() + + // Team (child of About) is visible while expanded. + expect(screen.getByText('Team')).not.toBeNull() + + await user.click(screen.getByRole('button', { name: 'Collapse' })) + expect(screen.queryByText('Team')).toBeNull() + + await user.click(screen.getByRole('button', { name: 'Expand' })) + expect(screen.getByText('Team')).not.toBeNull() + }) + + it('renders a read-only menu without editing affordances in preview', () => { + const pages = buildPages() + render() + + expect(screen.getByText('Menu')).not.toBeNull() + expect( + screen.queryByRole('button', { name: 'Add page' }) + ).toBeNull() + expect( + screen.queryByRole('button', { name: 'Rename Home' }) + ).toBeNull() + }) + + it('reorders siblings via drag and drop within the same level', () => { + const pages = buildPages() + const onReorder = jest.fn() + render() + + const homeHandle = screen.getByLabelText('Reorder Home') + const contactRow = screen.getByText('Contact').closest('div') as HTMLElement + + fireEvent.dragStart(homeHandle) + fireEvent.dragOver(contactRow) + fireEvent.drop(contactRow) + + expect(onReorder).toHaveBeenCalledWith(null, 0, 2) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/Navbar.test.tsx b/packages/dm-core-plugins/src/builder/tests/Navbar.test.tsx new file mode 100644 index 000000000..b46c4e58a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/Navbar.test.tsx @@ -0,0 +1,178 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { Navbar } from '../components/Navbar' +import { + createDefaultNavbar, + createPage, + type TBuilderPage, + type TNavbar, +} from '../model/site' + +const pages: TBuilderPage[] = [createPage('Home'), createPage('About')] + +const enabledNavbar = (overrides: Partial = {}): TNavbar => ({ + ...createDefaultNavbar(), + enabled: true, + items: [ + { id: 'nav-1', label: 'Home', target: { kind: 'page', pageId: pages[0].id } }, + ], + ...overrides, +}) + +const noop = () => {} + +const baseProps = (navbar: TNavbar) => ({ + navbar, + pages, + editing: true, + onNavigate: noop, + onUpdateNavbar: noop, + onAddItem: noop, + onUpdateItem: noop, + onRemoveItem: noop, + onReorderItem: noop, +}) + +describe('Navbar', () => { + it('renders an opt-in affordance while disabled in edit mode', async () => { + const user = userEvent.setup() + const onUpdateNavbar = jest.fn() + render( + + ) + + await user.click(screen.getByRole('button', { name: /Add a top navbar/i })) + expect(onUpdateNavbar).toHaveBeenCalledWith({ enabled: true }) + }) + + it('renders nothing when disabled in preview mode', () => { + const { container } = render( + + ) + expect(container.textContent).toBe('') + }) + + it('adds a link', async () => { + const user = userEvent.setup() + const onAddItem = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: /Add link/i })) + expect(onAddItem).toHaveBeenCalledTimes(1) + }) + + it('renames a link inline and commits on Enter', async () => { + const user = userEvent.setup() + const onUpdateItem = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Home' })) + const input = screen.getByRole('textbox') + await user.clear(input) + await user.type(input, 'Start{Enter}') + + expect(onUpdateItem).toHaveBeenCalledWith('nav-1', { label: 'Start' }) + }) + + it('retargets a link to another page', async () => { + const onUpdateItem = jest.fn() + render() + + const select = screen.getByLabelText('Target for Home') + fireEvent.change(select, { target: { value: pages[1].id } }) + + expect(onUpdateItem).toHaveBeenCalledWith('nav-1', { + target: { kind: 'page', pageId: pages[1].id }, + }) + }) + + it('switches a link to an external URL target', async () => { + const onUpdateItem = jest.fn() + render() + + const select = screen.getByLabelText('Target for Home') + fireEvent.change(select, { target: { value: '__url__' } }) + + expect(onUpdateItem).toHaveBeenCalledWith('nav-1', { + target: { kind: 'url', href: '' }, + }) + }) + + it('deletes a link', async () => { + const user = userEvent.setup() + const onRemoveItem = jest.fn() + render() + + await user.click(screen.getByRole('button', { name: 'Delete Home' })) + expect(onRemoveItem).toHaveBeenCalledWith('nav-1') + }) + + it('edits the brand inline', async () => { + const user = userEvent.setup() + const onUpdateNavbar = jest.fn() + render( + + ) + + await user.click(screen.getByText('My Site')) + const input = screen.getByDisplayValue('My Site') + await user.clear(input) + await user.type(input, 'Acme{Enter}') + + expect(onUpdateNavbar).toHaveBeenCalledWith({ brand: 'Acme' }) + }) + + it('navigates when a preview link is clicked', async () => { + const user = userEvent.setup() + const onNavigate = jest.fn() + render( + + ) + + await user.click(screen.getByRole('button', { name: 'Home' })) + expect(onNavigate).toHaveBeenCalledWith(pages[0].id) + }) + + describe('accessibility', () => { + it('exposes the settings popover as an expandable dialog', async () => { + const user = userEvent.setup() + render() + + const toggle = screen.getByRole('button', { name: 'Navbar settings' }) + expect(toggle.getAttribute('aria-haspopup')).toBe('dialog') + expect(toggle.getAttribute('aria-expanded')).toBe('false') + + await user.click(toggle) + + expect(toggle.getAttribute('aria-expanded')).toBe('true') + const dialog = screen.getByRole('dialog', { name: 'Navbar settings' }) + expect(dialog).not.toBeNull() + }) + + it('closes the settings popover on Escape', async () => { + const user = userEvent.setup() + render() + + await user.click(screen.getByRole('button', { name: 'Navbar settings' })) + expect(screen.queryByRole('dialog')).not.toBeNull() + + await user.keyboard('{Escape}') + expect(screen.queryByRole('dialog')).toBeNull() + }) + + it('labels the drag handle and destructive actions for each link', () => { + render() + expect(screen.getByLabelText('Reorder Home')).not.toBeNull() + expect( + screen.getByRole('button', { name: 'Delete Home' }) + ).not.toBeNull() + }) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/Outline.test.tsx b/packages/dm-core-plugins/src/builder/tests/Outline.test.tsx new file mode 100644 index 000000000..895206053 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/Outline.test.tsx @@ -0,0 +1,59 @@ +import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { getBlock } from '../model/blocks' +import { Outline } from '../components/Outline' +import { addWidget, createEmptyModel } from '../model/model' + +const modelWith = (...blockIds: string[]) => + blockIds.reduce((model, id) => { + const block = getBlock(id) + if (!block) throw new Error(`missing block ${id}`) + return addWidget(model, block) + }, createEmptyModel()) + +describe('Outline', () => { + const noop = () => {} + + it('shows an empty hint when there are no widgets', () => { + render( + + ) + expect(screen.getByText('No widgets on this page yet.')).not.toBeNull() + }) + + it('lists a row per widget and selects on click', async () => { + const user = userEvent.setup() + const model = modelWith('heading', 'text') + const onSelect = jest.fn() + render( + + ) + + await user.click(screen.getByText('Text')) + expect(onSelect).toHaveBeenCalledWith(1) + }) + + it('opens a container section via its Open button without selecting', async () => { + const user = userEvent.setup() + const model = modelWith('section') + const onSelect = jest.fn() + const onEnter = jest.fn() + render( + + ) + + await user.click(screen.getByRole('button', { name: 'Open section' })) + expect(onEnter).toHaveBeenCalledWith(0) + expect(onSelect).not.toHaveBeenCalled() + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/SiteDirectory.test.tsx b/packages/dm-core-plugins/src/builder/tests/SiteDirectory.test.tsx new file mode 100644 index 000000000..cd5cf44b8 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/SiteDirectory.test.tsx @@ -0,0 +1,253 @@ +import { + DMApplicationProvider, + DmssAPI, + type TUiPluginMap, +} from '@development-framework/dm-core' +import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import type { ReactNode } from 'react' +import * as SiteDirectoryModule from '../SiteDirectoryPlugin' +import { SiteDirectoryPlugin } from '../SiteDirectoryPlugin' +import { SITE_TYPE_ADDRESS } from '../model/site' +import type { TSiteDirectoryConfig } from '../types/siteDirectory' + +const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + +) + +const renderDirectory = (config?: TSiteDirectoryConfig) => + render( + , + { wrapper } + ) + +const mockSearch = (data: Record) => + jest + .spyOn(DmssAPI.prototype, 'search') + // biome-ignore lint/suspicious/noExplicitAny: test stub for AxiosPromise + .mockResolvedValue({ data } as any) + +const baseConfig: TSiteDirectoryConfig = { + dataSources: ['Sites'], + targetDataSource: 'Sites', +} + +describe('SiteDirectoryPlugin', () => { + afterEach(() => { + jest.restoreAllMocks() + }) + + it('lists sites returned by search with an Open link to the viewer', async () => { + const search = mockSearch({ + 'Sites/marketing': { _id: 'marketing', name: 'Marketing site' }, + 'Sites/docs': { _id: 'docs', name: 'Docs portal' }, + }) + + renderDirectory(baseConfig) + + expect(await screen.findByText('Marketing site')).not.toBeNull() + expect(screen.getByText('Docs portal')).not.toBeNull() + + // Searched for the fully-qualified Site type in the configured data source. + expect(search).toHaveBeenCalledWith( + expect.objectContaining({ + dataSources: ['Sites'], + body: { type: SITE_TYPE_ADDRESS }, + }) + ) + + // Each card links to the default viewer URL for its entity ($ = root id). + const openLinks = screen.getAllByRole('link') + const hrefs = openLinks.map((a) => a.getAttribute('href')) + expect(hrefs).toContain('/view?documentId=dmss://Sites/$marketing') + expect(hrefs).toContain('/view?documentId=dmss://Sites/$docs') + }) + + it('applies a custom viewUrlTemplate', async () => { + mockSearch({ 'Sites/one': { _id: 'one', name: 'One' } }) + + renderDirectory({ ...baseConfig, viewUrlTemplate: '/sites/{id}' }) + + const link = await screen.findByRole('link') + expect(link.getAttribute('href')).toBe('/sites/one') + }) + + it('shows an empty state when there are no sites', async () => { + mockSearch({}) + + renderDirectory(baseConfig) + + expect(await screen.findByText(/No websites yet/)).not.toBeNull() + }) + + it('shows an error state when search fails', async () => { + jest + .spyOn(DmssAPI.prototype, 'search') + .mockRejectedValue(new Error('boom')) + jest.spyOn(console, 'error').mockImplementation(() => {}) + + renderDirectory(baseConfig) + + expect(await screen.findByText(/Could not load websites/)).not.toBeNull() + expect(screen.getByRole('alert')).not.toBeNull() + }) + + it('creates a new site and navigates to it', async () => { + mockSearch({}) + const documentAddSimple = jest + .spyOn(DmssAPI.prototype, 'documentAddSimple') + // biome-ignore lint/suspicious/noExplicitAny: test stub for AxiosPromise + .mockResolvedValue({ data: 'fresh-id' } as any) + + const assign = jest + .spyOn(SiteDirectoryModule, 'navigateTo') + .mockImplementation(() => {}) + + renderDirectory(baseConfig) + + const button = await screen.findByRole('button', { name: 'New site' }) + fireEvent.click(button) + + await waitFor(() => expect(documentAddSimple).toHaveBeenCalledTimes(1)) + + const arg = documentAddSimple.mock.calls[0][0] as { + dataSourceId: string + // biome-ignore lint/suspicious/noExplicitAny: raw add body + body: any + } + expect(arg.dataSourceId).toBe('Sites') + // The raw endpoint stores verbatim, so the type must be fully-qualified. + expect(arg.body.type).toBe(SITE_TYPE_ADDRESS) + // Name is slugged (no spaces) with a unique suffix. + expect(typeof arg.body.name).toBe('string') + expect(arg.body.name).not.toContain(' ') + expect(Array.isArray(arg.body.pages)).toBe(true) + + await waitFor(() => + expect(assign).toHaveBeenCalledWith( + '/view?documentId=dmss://Sites/$fresh-id' + ) + ) + }) + + it('opens a freshly created site via newSiteUrlTemplate when set', async () => { + mockSearch({}) + jest + .spyOn(DmssAPI.prototype, 'documentAddSimple') + // biome-ignore lint/suspicious/noExplicitAny: test stub for AxiosPromise + .mockResolvedValue({ data: 'fresh-id' } as any) + + const assign = jest + .spyOn(SiteDirectoryModule, 'navigateTo') + .mockImplementation(() => {}) + + renderDirectory({ + ...baseConfig, + newSiteUrlTemplate: + '/view?documentId=dmss://{dataSource}/${id}&recipe=Edit', + }) + + fireEvent.click(await screen.findByRole('button', { name: 'New site' })) + + await waitFor(() => + expect(assign).toHaveBeenCalledWith( + '/view?documentId=dmss://Sites/$fresh-id&recipe=Edit' + ) + ) + }) + + it('shows the friendly title, a Draft badge, and a Show drafts toggle', async () => { + mockSearch({ + 'Sites/live': { + _id: 'live', + name: 'Live_Slug', + title: 'Live Marketing Site', + published: true, + }, + 'Sites/draft': { _id: 'draft', name: 'Draft_Slug', published: false }, + }) + + renderDirectory(baseConfig) + + // Friendly title is preferred over the slug name. + expect(await screen.findByText('Live Marketing Site')).not.toBeNull() + // Draft site with no title falls back to its slug name. + expect(screen.getByText('Draft_Slug')).not.toBeNull() + // Only the unpublished site carries a Draft badge. + expect(screen.getAllByText('Draft')).toHaveLength(1) + + // Turning off "Show drafts" hides the draft, leaving only the published one. + const toggle = screen.getByLabelText('Show drafts') as HTMLInputElement + expect(toggle.checked).toBe(true) + fireEvent.click(toggle) + + await waitFor(() => expect(screen.queryByText('Draft_Slug')).toBeNull()) + expect(screen.getByText('Live Marketing Site')).not.toBeNull() + }) + + it('deletes a site after modal confirmation and removes the card', async () => { + mockSearch({ + 'Sites/alpha': { _id: 'alpha', name: 'Alpha', published: true }, + 'Sites/beta': { _id: 'beta', name: 'Beta', published: true }, + }) + const documentRemove = jest + .spyOn(DmssAPI.prototype, 'documentRemove') + // biome-ignore lint/suspicious/noExplicitAny: test stub + .mockResolvedValue({ data: undefined } as any) + + renderDirectory(baseConfig) + + expect(await screen.findByText('Alpha')).not.toBeNull() + expect(screen.getByText('Beta')).not.toBeNull() + + // Click the trash icon — the modal should appear. + const [firstTrash] = screen.getAllByRole('button', { name: 'Delete site' }) + fireEvent.click(firstTrash) + expect(documentRemove).not.toHaveBeenCalled() + + // Modal shows the site name and a confirmation message. + expect(screen.getByRole('dialog')).not.toBeNull() + expect(screen.getByText(/permanent/i)).not.toBeNull() + + // Clicking "Delete" executes the deletion. + fireEvent.click(screen.getByRole('button', { name: 'Delete' })) + + await waitFor(() => expect(documentRemove).toHaveBeenCalledTimes(1)) + // The deleted card disappears from the list and the modal closes. + await waitFor(() => expect(screen.queryByText('Alpha')).toBeNull()) + expect(screen.queryByRole('dialog')).toBeNull() + expect(screen.getByText('Beta')).not.toBeNull() + }) + + it('cancels deletion when "No" is clicked in the modal', async () => { + mockSearch({ + 'Sites/one': { _id: 'one', name: 'One', published: true }, + }) + const documentRemove = jest + .spyOn(DmssAPI.prototype, 'documentRemove') + // biome-ignore lint/suspicious/noExplicitAny: test stub + .mockResolvedValue({ data: undefined } as any) + + renderDirectory(baseConfig) + + await screen.findByText('One') + fireEvent.click(screen.getByRole('button', { name: 'Delete site' })) + expect(screen.getByRole('dialog')).not.toBeNull() + + fireEvent.click(screen.getByRole('button', { name: 'Cancel' })) + + await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull()) + expect(documentRemove).not.toHaveBeenCalled() + expect(screen.getByText('One')).not.toBeNull() + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/SiteGrid.test.tsx b/packages/dm-core-plugins/src/builder/tests/SiteGrid.test.tsx new file mode 100644 index 000000000..4afb13988 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/SiteGrid.test.tsx @@ -0,0 +1,104 @@ +import { + DMApplicationProvider, + type IUIPlugin, + type TUiPluginMap, +} from '@development-framework/dm-core' +import { render, screen } from '@testing-library/react' +import type { ReactNode } from 'react' +import type { TGridPluginConfig } from '../../grid/types' +import { GRID_PLUGIN_NAME } from '../model/model' +import { SiteGrid } from '../components/SiteGrid' + +const HeadingStub = (props: IUIPlugin) => ( + + {(props.config as { text?: string })?.text ?? ''} + +) + +const plugins = { + 'test/heading': { component: HeadingStub }, + // Containers render their nested grid recursively, not through this plugin. + [GRID_PLUGIN_NAME]: { + component: () => , + }, +} as unknown as TUiPluginMap + +const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + +) + +const gridWith = (items: TGridPluginConfig['items']): TGridPluginConfig => ({ + size: { columns: 12, rows: 4, rowGap: '8px', columnGap: '8px' }, + items, + itemBorder: { size: '1px', style: 'solid', color: '#bbb', radius: '4px' }, + showItemBorders: false, +}) + +const headingItem = (text: string) => ({ + type: 'dmss://system/Plugins/dm-core-plugins/grid/GridItem', + gridArea: { rowStart: 1, rowEnd: 1, columnStart: 1, columnEnd: 6 }, + viewConfig: { + type: 'CORE:InlineRecipeViewConfig', + scope: '', + recipe: { + type: 'CORE:UiRecipe', + name: 'heading', + plugin: 'test/heading', + config: { text }, + }, + }, +}) + +describe('SiteGrid', () => { + it('renders a self-contained widget directly from its inline config', () => { + render( + , + { wrapper } + ) + + // The plugin renders synchronously with the inline config — no spinner and + // no DMSS attributeGet that would fail on the free-form config. + expect(screen.getByTestId('heading').textContent).toBe('Welcome') + expect(screen.queryByRole('progressbar')).toBeNull() + }) + + it('renders nested section grids recursively', () => { + const section = { + type: 'dmss://system/Plugins/dm-core-plugins/grid/GridItem', + gridArea: { rowStart: 1, rowEnd: 2, columnStart: 1, columnEnd: 12 }, + viewConfig: { + type: 'CORE:InlineRecipeViewConfig', + scope: '', + recipe: { + type: 'CORE:UiRecipe', + name: 'section', + plugin: GRID_PLUGIN_NAME, + config: gridWith([headingItem('Nested')]), + }, + }, + } + + render( + , + { wrapper } + ) + + expect(screen.getByTestId('heading').textContent).toBe('Nested') + expect(screen.queryByTestId('should-not-render')).toBeNull() + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/WidgetPalette.test.tsx b/packages/dm-core-plugins/src/builder/tests/WidgetPalette.test.tsx new file mode 100644 index 000000000..0ea5a1f9b --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/WidgetPalette.test.tsx @@ -0,0 +1,41 @@ +import { DndContext } from '@dnd-kit/core' +import { fireEvent, render, screen } from '@testing-library/react' +import { BLOCKS } from '../model/blocks' +import { WidgetPalette } from '../components/WidgetPalette' + +const renderPalette = (onAdd: (block: unknown) => void) => + render( + + + + ) + +describe('WidgetPalette', () => { + it('renders a card for every registered block', () => { + renderPalette(() => {}) + for (const block of BLOCKS) { + expect(screen.getByRole('button', { name: block.label })).not.toBeNull() + } + }) + + it('groups blocks under their category headings', () => { + renderPalette(() => {}) + // Every block declares one of these categories; at least Layout/Content exist. + expect(screen.getByText('Layout')).not.toBeNull() + expect(screen.getByText('Content')).not.toBeNull() + }) + + it('calls onAdd with the block when a card is clicked', async () => { + const onAdd = jest.fn() + renderPalette(onAdd) + + const section = BLOCKS.find((block) => block.id === 'section') + if (!section) throw new Error('section block missing') + // Click directly: dnd-kit draggable listeners intercept the pointer + // sequence user-event emits, but the card's onClick still fires on click. + fireEvent.click(screen.getByRole('button', { name: section.label })) + + expect(onAdd).toHaveBeenCalledTimes(1) + expect(onAdd).toHaveBeenCalledWith(section) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/gridMetrics.test.ts b/packages/dm-core-plugins/src/builder/tests/gridMetrics.test.ts new file mode 100644 index 000000000..1e2f3afd1 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/gridMetrics.test.ts @@ -0,0 +1,40 @@ +import type { TGridSize } from '../../grid/types' +import { pixelDeltaToCells } from '../utils/gridMetrics' + +describe('builder grid metrics', () => { + const size: TGridSize = { + columns: 12, + rows: 8, + rowGap: '16px', + columnGap: '16px', + } + + it('converts pixel deltas to rounded cell deltas', () => { + const rect = { width: 232, height: 152 } + // width stride: ((232 - 2 * 4) + 16) / 12 = 20 + // height stride: ((152 - 2 * 4) + 16) / 8 = 20 + expect(pixelDeltaToCells({ x: 40, y: 60 }, rect, size)).toEqual({ + columns: 2, + rows: 3, + }) + expect(pixelDeltaToCells({ x: -40, y: -20 }, rect, size)).toEqual({ + columns: -2, + rows: -1, + }) + expect(pixelDeltaToCells({ x: 9, y: -9 }, rect, size)).toEqual({ + columns: 0, + rows: 0, + }) + }) + + it('returns zero for tracks with degenerate stride', () => { + const noGapSize: TGridSize = { ...size, rowGap: '0px', columnGap: '0px' } + + expect( + pixelDeltaToCells({ x: 40, y: 23 }, { width: 0, height: 100 }, noGapSize) + ).toEqual({ columns: 0, rows: 2 }) + expect( + pixelDeltaToCells({ x: 15, y: 40 }, { width: 100, height: 0 }, noGapSize) + ).toEqual({ columns: 2, rows: 0 }) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/history.test.ts b/packages/dm-core-plugins/src/builder/tests/history.test.ts new file mode 100644 index 000000000..10080df79 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/history.test.ts @@ -0,0 +1,133 @@ +import { + breakHistory, + canRedo, + canUndo, + createHistory, + HISTORY_LIMIT, + pushHistory, + redoHistory, + undoHistory, +} from '../model/history' + +describe('builder history', () => { + it('creates history with the initial present value and empty stacks', () => { + const present = { id: 'initial' } + const history = createHistory(present) + + expect(history).toEqual({ + past: [], + present, + future: [], + lastLabel: null, + }) + expect(canUndo(history)).toBe(false) + expect(canRedo(history)).toBe(false) + }) + + it('pushes the current present onto past, updates present, and clears future', () => { + const initial = createHistory('A') + const withFuture = undoHistory(pushHistory(pushHistory(initial, 'B'), 'C')) + const result = pushHistory(withFuture, 'D') + + expect(result).toEqual({ + past: ['A', 'B'], + present: 'D', + future: [], + lastLabel: null, + }) + expect(canUndo(result)).toBe(true) + expect(canRedo(result)).toBe(false) + }) + + it('does nothing when pushing the same present reference', () => { + const present = { id: 'same' } + const history = createHistory(present) + + expect(pushHistory(history, present, 'edit')).toBe(history) + }) + + it('trims past entries to the history limit and drops the oldest entries', () => { + let history = createHistory(0) + for (let next = 1; next <= HISTORY_LIMIT + 5; next++) { + history = pushHistory(history, next) + } + + expect(history.past).toHaveLength(HISTORY_LIMIT) + expect(history.past[0]).toBe(5) + expect(history.past[history.past.length - 1]).toBe(HISTORY_LIMIT + 4) + expect(history.present).toBe(HISTORY_LIMIT + 5) + }) + + it('coalesces consecutive non-null labels by replacing present without growing past', () => { + const first = pushHistory(createHistory('A'), 'B', 'drag') + const coalesced = pushHistory(first, 'C', 'drag') + const differentLabel = pushHistory(coalesced, 'D', 'resize') + const nullLabel = pushHistory(differentLabel, 'E') + + expect(first.past).toEqual(['A']) + expect(coalesced.past).toEqual(['A']) + expect(coalesced.present).toBe('C') + expect(differentLabel.past).toEqual(['A', 'C']) + expect(differentLabel.present).toBe('D') + expect(nullLabel.past).toEqual(['A', 'C', 'D']) + expect(nullLabel.present).toBe('E') + }) + + it('moves present through past and future for undo and redo', () => { + const history = pushHistory(pushHistory(createHistory('A'), 'B'), 'C') + + const firstUndo = undoHistory(history) + expect(firstUndo).toEqual({ + past: ['A'], + present: 'B', + future: ['C'], + lastLabel: null, + }) + + const secondUndo = undoHistory(firstUndo) + expect(secondUndo).toEqual({ + past: [], + present: 'A', + future: ['B', 'C'], + lastLabel: null, + }) + expect(undoHistory(secondUndo)).toBe(secondUndo) + + const firstRedo = redoHistory(secondUndo) + expect(firstRedo).toEqual({ + past: ['A'], + present: 'B', + future: ['C'], + lastLabel: null, + }) + + const secondRedo = redoHistory(firstRedo) + expect(secondRedo.present).toBe('C') + expect(secondRedo.future).toEqual([]) + expect(redoHistory(secondRedo)).toBe(secondRedo) + }) + + it('clears future when pushing a fresh value after undo', () => { + const history = pushHistory(pushHistory(createHistory('A'), 'B'), 'C') + const undone = undoHistory(history) + const result = pushHistory(undone, 'D') + + expect(result).toEqual({ + past: ['A', 'B'], + present: 'D', + future: [], + lastLabel: null, + }) + }) + + it('breaks a coalescing run so the next same-label push creates a new step', () => { + const first = pushHistory(createHistory('A'), 'B', 'typing') + const broken = breakHistory(first) + const result = pushHistory(broken, 'C', 'typing') + + expect(broken.lastLabel).toBeNull() + expect(result.past).toEqual(['A', 'B']) + expect(result.present).toBe('C') + expect(result.lastLabel).toBe('typing') + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/markdownTable.test.ts b/packages/dm-core-plugins/src/builder/tests/markdownTable.test.ts new file mode 100644 index 000000000..c53be8350 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/markdownTable.test.ts @@ -0,0 +1,78 @@ +import { markdownToRows, rowsToMarkdown } from '../utils/markdownTable' + +describe('markdown table', () => { + it('renders a cell grid as a GitHub-style markdown table', () => { + const md = rowsToMarkdown([ + ['Name', 'Score'], + ['Ada', '99'], + ['Linus', '42'], + ]) + expect(md).toBe( + [ + '| Name | Score |', + '| --- | --- |', + '| Ada | 99 |', + '| Linus | 42 |', + ].join('\n') + ) + }) + + it('parses a markdown table into a cell grid, ignoring the separator', () => { + const rows = markdownToRows( + '| Name | Score |\n| --- | --- |\n| Ada | 99 |\n| Linus | 42 |' + ) + expect(rows).toEqual([ + ['Name', 'Score'], + ['Ada', '99'], + ['Linus', '42'], + ]) + }) + + it('round-trips a grid through markdown and back', () => { + const original = [ + ['A', 'B', 'C'], + ['1', '2', '3'], + ['4', '5', '6'], + ] + expect(markdownToRows(rowsToMarkdown(original))).toEqual(original) + }) + + it('escapes and restores pipes inside cells', () => { + const original = [ + ['Expression', 'Result'], + ['a | b', 'true'], + ] + const md = rowsToMarkdown(original) + expect(md).toContain('a \\| b') + expect(markdownToRows(md)).toEqual(original) + }) + + it('pads ragged rows to the widest row when rendering', () => { + const md = rowsToMarkdown([['A', 'B'], ['only-one']]) + expect(md).toBe( + ['| A | B |', '| --- | --- |', '| only-one | |'].join('\n') + ) + }) + + it('tolerates missing separators and surrounding pipes', () => { + const rows = markdownToRows('Name | Score\nAda | 99') + expect(rows).toEqual([ + ['Name', 'Score'], + ['Ada', '99'], + ]) + }) + + it('skips blank lines and trims surrounding whitespace', () => { + const rows = markdownToRows('\n | A | B | \n\n| 1 | 2 |\n') + expect(rows).toEqual([ + ['A', 'B'], + ['1', '2'], + ]) + }) + + it('returns an empty grid for empty input and an empty string for no rows', () => { + expect(markdownToRows('')).toEqual([]) + expect(markdownToRows(' \n ')).toEqual([]) + expect(rowsToMarkdown([])).toBe('') + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/mathUtils.test.ts b/packages/dm-core-plugins/src/builder/tests/mathUtils.test.ts new file mode 100644 index 000000000..3898db76d --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/mathUtils.test.ts @@ -0,0 +1,60 @@ +import { + aggregate, + formatNumber, + max, + mean, + median, + min, + parseNumbers, + stddev, + sum, +} from '../utils/mathUtils' + +describe('math utils', () => { + it('parses finite numbers from free-form text', () => { + expect(parseNumbers('1, 2, 3')).toEqual([1, 2, 3]) + expect(parseNumbers('1 2\n3.5\t-4')).toEqual([1, 2, 3.5, -4]) + expect(parseNumbers('a 1 b 2 c')).toEqual([1, 2]) + expect(parseNumbers('1e3 2.5e-1')).toEqual([1000, 0.25]) + expect(parseNumbers('nothing here')).toEqual([]) + }) + + it('computes basic aggregations', () => { + const values = [2, 4, 4, 4, 5, 5, 7, 9] + expect(sum(values)).toBe(40) + expect(mean(values)).toBe(5) + expect(min(values)).toBe(2) + expect(max(values)).toBe(9) + expect(median(values)).toBe(4.5) + expect(stddev(values)).toBe(2) + }) + + it('returns the middle value for odd-length medians', () => { + expect(median([3, 1, 2])).toBe(2) + }) + + it('guards empty input with zero', () => { + expect(sum([])).toBe(0) + expect(mean([])).toBe(0) + expect(min([])).toBe(0) + expect(max([])).toBe(0) + expect(median([])).toBe(0) + expect(stddev([])).toBe(0) + }) + + it('dispatches aggregations by name', () => { + const values = [1, 2, 3, 4] + expect(aggregate(values, 'sum')).toBe(10) + expect(aggregate(values, 'mean')).toBe(2.5) + expect(aggregate(values, 'min')).toBe(1) + expect(aggregate(values, 'max')).toBe(4) + expect(aggregate(values, 'count')).toBe(4) + }) + + it('formats numbers to fixed decimals without trailing zeros', () => { + expect(formatNumber(Math.PI, 2)).toBe('3.14') + expect(formatNumber(5, 2)).toBe('5') + expect(formatNumber(2.5, 0)).toBe('3') + expect(formatNumber(Number.NaN)).toBe('–') + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/model.test.ts b/packages/dm-core-plugins/src/builder/tests/model.test.ts new file mode 100644 index 000000000..1b761f116 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/model.test.ts @@ -0,0 +1,893 @@ +import { BLOCKS, getBlock } from '../model/blocks' +import { + addWidget, + areasOverlap, + clampArea, + clampPath, + createEmptyModel, + deserialize, + duplicateWidget, + ensureModel, + findFreeArea, + GRID_AREA_TYPE, + GRID_CONFIG_TYPE, + GRID_ITEM_TYPE, + GRID_SIZE_TYPE, + getSubModel, + getWidgetConfigValue, + getWidgetStyleValue, + INLINE_RECIPE_VIEW_CONFIG_TYPE, + insertWidgetItem, + isContainerItem, + moveWidget, + REFERENCE_VIEW_CONFIG_TYPE, + removeWidget, + rescaleGrid, + resizeWidget, + serialize, + setSubModel, + setWidgetArea, + setWidgetConfigValue, + setWidgetLabel, + setWidgetScope, + setWidgetStyleValue, + setWidgetTitle, + translateArea, + wouldOverlap, +} from '../model/model' + +import type { TBlock } from '../types' + +const textBlock = getBlock('text') ?? BLOCKS[0] +const imageBlock = getBlock('image') ?? BLOCKS[1] +const sectionBlock = getBlock('section') ?? BLOCKS[0] +// A minimal block that ships no defaultConfig, so its inline recipe starts +// without a `config` object — used to test the config setters from scratch. +const configlessBlock: TBlock = { + id: 'test-configless', + label: 'Configless', + icon: 'text_field', + category: 'content', + description: 'Test-only block with no default config.', + contentModel: 'content', + defaultSize: { columns: 4, rows: 2 }, + recipe: '@development-framework/dm-core-plugins/markdown', +} +const clone = (value: T): T => JSON.parse(JSON.stringify(value)) + +describe('builder model', () => { + it('creates an empty 24x16 model with expected defaults', () => { + expect(createEmptyModel()).toEqual({ + size: { + columns: 24, + rows: 16, + rowGap: '8px', + columnGap: '8px', + }, + items: [], + itemBorder: { + size: '1px', + style: 'solid', + color: '#bbb', + radius: '5px', + }, + showItemBorders: false, + }) + }) + + it('adds a widget without mutating the original model', () => { + const model = createEmptyModel() + const original = clone(model) + const result = addWidget(model, textBlock, { + rowStart: 7, + rowEnd: 10, + columnStart: 11, + columnEnd: 15, + }) + + expect(model).toEqual(original) + expect(result).not.toBe(model) + expect(result.items).toHaveLength(1) + expect(result.items[0]).toEqual({ + type: GRID_ITEM_TYPE, + title: textBlock.label, + gridArea: { + rowStart: 7, + rowEnd: 10, + columnStart: 11, + columnEnd: 15, + }, + viewConfig: { + type: INLINE_RECIPE_VIEW_CONFIG_TYPE, + scope: '', + label: textBlock.label, + recipe: { + name: textBlock.id, + type: 'CORE:UiRecipe', + plugin: textBlock.recipe, + config: { content: 'Write your text here.' }, + }, + }, + }) + }) + + it('places a second widget in a different non-overlapping area', () => { + const model = addWidget(createEmptyModel(), textBlock) + const result = addWidget(model, textBlock) + + expect(result.items).toHaveLength(2) + expect(result.items[1].gridArea).not.toEqual(result.items[0].gridArea) + expect( + areasOverlap(result.items[0].gridArea, result.items[1].gridArea) + ).toBe(false) + }) + + it('removes the correct widget without mutating the original model', () => { + const model = addWidget( + addWidget(createEmptyModel(), textBlock), + imageBlock + ) + const original = clone(model) + const result = removeWidget(model, 0) + + expect(model).toEqual(original) + expect(result).not.toBe(model) + expect(result.items).toEqual([model.items[1]]) + }) + + it('moves a widget and clamps out-of-bounds areas inside the grid', () => { + const model = addWidget(createEmptyModel(), textBlock) + const original = clone(model) + const result = moveWidget(model, 0, { + rowStart: 6, + rowEnd: 12, + columnStart: -2, + columnEnd: 4, + }) + + expect(model).toEqual(original) + expect(result.items[0].gridArea).toEqual({ + rowStart: 6, + rowEnd: 12, + columnStart: 1, + columnEnd: 7, + }) + }) + + it('translates an area by positive and negative deltas while preserving its span', () => { + const area = { rowStart: 3, rowEnd: 6, columnStart: 4, columnEnd: 8 } + const result = translateArea(area, -2, 5) + + expect(result).toEqual({ + rowStart: 8, + rowEnd: 11, + columnStart: 2, + columnEnd: 6, + }) + expect(result.columnEnd - result.columnStart).toBe( + area.columnEnd - area.columnStart + ) + expect(result.rowEnd - result.rowStart).toBe(area.rowEnd - area.rowStart) + }) + + it('resizes a widget without mutating the input model or other items', () => { + const model = addWidget( + addWidget(createEmptyModel(), textBlock, { + rowStart: 2, + rowEnd: 3, + columnStart: 2, + columnEnd: 3, + }), + imageBlock, + { rowStart: 5, rowEnd: 6, columnStart: 5, columnEnd: 6 } + ) + const original = clone(model) + + const grown = resizeWidget(model, 0, { + rowStart: 2, + rowEnd: 5, + columnStart: 2, + columnEnd: 7, + }) + const shrunk = resizeWidget(model, 0, { + rowStart: 2, + rowEnd: 2, + columnStart: 2, + columnEnd: 2, + }) + + expect(model).toEqual(original) + expect(grown).not.toBe(model) + expect(grown.items[0].gridArea).toEqual({ + rowStart: 2, + rowEnd: 5, + columnStart: 2, + columnEnd: 7, + }) + expect(grown.items[1]).toEqual(model.items[1]) + expect(shrunk.items[0].gridArea).toEqual({ + rowStart: 2, + rowEnd: 2, + columnStart: 2, + columnEnd: 2, + }) + }) + + it('normalizes resize areas to at least 1x1 and clamps them to grid bounds', () => { + const model = addWidget(createEmptyModel(), textBlock) + + const normalized = resizeWidget(model, 0, { + rowStart: 4, + rowEnd: 1, + columnStart: 5, + columnEnd: 1, + }) + const clamped = resizeWidget(model, 0, { + rowStart: 7, + rowEnd: 12, + columnStart: 10, + columnEnd: 15, + }) + + expect(normalized.items[0].gridArea).toEqual({ + rowStart: 4, + rowEnd: 4, + columnStart: 5, + columnEnd: 5, + }) + expect(clamped.items[0].gridArea).toEqual({ + rowStart: 7, + rowEnd: 12, + columnStart: 10, + columnEnd: 15, + }) + }) + + it('duplicates a widget into free space with an isolated view config copy', () => { + const model = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 2, + columnStart: 1, + columnEnd: 2, + }) + const original = clone(model) + const result = duplicateWidget(model, 0) + + expect(model).toEqual(original) + expect(result).not.toBe(model) + expect(result.items).toHaveLength(2) + expect( + areasOverlap(result.items[0].gridArea, result.items[1].gridArea) + ).toBe(false) + + result.items[1].viewConfig.label = 'Changed copy' + expect(result.items[0].viewConfig.label).toBe(textBlock.label) + }) + + it('returns an equal model when duplicating an out-of-range widget index', () => { + const model = addWidget(createEmptyModel(), textBlock) + + expect(duplicateWidget(model, 99)).toEqual(model) + }) + + it('grows the grid and avoids overlap when duplicating into a full grid', () => { + const full = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 16, + columnStart: 1, + columnEnd: 24, + }) + const result = duplicateWidget(full, 0) + + expect(result.size.rows).toBeGreaterThan(full.size.rows) + expect(result.items).toHaveLength(2) + expect( + areasOverlap(result.items[0].gridArea, result.items[1].gridArea) + ).toBe(false) + }) + + it('detects overlap against other widgets while ignoring the excluded index', () => { + const model = addWidget( + addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 2, + columnStart: 1, + columnEnd: 2, + }), + imageBlock, + { rowStart: 4, rowEnd: 5, columnStart: 4, columnEnd: 5 } + ) + + expect( + wouldOverlap(model, 0, { + rowStart: 4, + rowEnd: 4, + columnStart: 4, + columnEnd: 4, + }) + ).toBe(true) + expect(wouldOverlap(model, 0, model.items[0].gridArea)).toBe(false) + expect( + wouldOverlap(model, 0, { + rowStart: 7, + rowEnd: 8, + columnStart: 10, + columnEnd: 12, + }) + ).toBe(false) + }) + + it('detects overlapping, adjacent, and disjoint areas', () => { + expect( + areasOverlap( + { rowStart: 1, rowEnd: 3, columnStart: 1, columnEnd: 3 }, + { rowStart: 2, rowEnd: 4, columnStart: 2, columnEnd: 4 } + ) + ).toBe(true) + expect( + areasOverlap( + { rowStart: 1, rowEnd: 2, columnStart: 1, columnEnd: 2 }, + { rowStart: 1, rowEnd: 2, columnStart: 3, columnEnd: 4 } + ) + ).toBe(false) + expect( + areasOverlap( + { rowStart: 1, rowEnd: 2, columnStart: 1, columnEnd: 2 }, + { rowStart: 4, rowEnd: 5, columnStart: 4, columnEnd: 5 } + ) + ).toBe(false) + }) + + it('keeps in-bounds areas unchanged when clamping', () => { + expect( + clampArea( + { rowStart: 2, rowEnd: 4, columnStart: 3, columnEnd: 6 }, + createEmptyModel().size + ) + ).toEqual({ rowStart: 2, rowEnd: 4, columnStart: 3, columnEnd: 6 }) + }) + + it('pulls out-of-bounds areas inside the grid while preserving width and height', () => { + expect( + clampArea( + { rowStart: 0, rowEnd: 2, columnStart: 11, columnEnd: 14 }, + createEmptyModel().size + ) + ).toEqual({ rowStart: 1, rowEnd: 3, columnStart: 11, columnEnd: 14 }) + }) + + it('clamps an area larger than the grid to fit inside it', () => { + const size = createEmptyModel().size + const clamped = clampArea( + { rowStart: 1, rowEnd: 20, columnStart: 1, columnEnd: 30 }, + size + ) + expect(clamped).toEqual({ + rowStart: 1, + rowEnd: size.rows, + columnStart: 1, + columnEnd: size.columns, + }) + }) + + it('returns null from findFreeArea when the grid is full', () => { + const model = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 16, + columnStart: 1, + columnEnd: 24, + }) + expect(findFreeArea(model, { columns: 1, rows: 1 })).toBeNull() + }) + + it('grows the grid and avoids overlap when adding to a full grid', () => { + const full = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 16, + columnStart: 1, + columnEnd: 24, + }) + const result = addWidget(full, imageBlock) + + expect(result.size.rows).toBeGreaterThan(full.size.rows) + expect(result.items).toHaveLength(2) + expect( + areasOverlap(result.items[0].gridArea, result.items[1].gridArea) + ).toBe(false) + }) + + it('round-trips through serialize and deserialize with type discriminators', () => { + const model = addWidget( + addWidget(createEmptyModel(), textBlock), + imageBlock + ) + const serialized = serialize(model) + + expect(serialized).toMatchObject({ + type: GRID_CONFIG_TYPE, + size: { type: GRID_SIZE_TYPE }, + items: [ + { type: GRID_ITEM_TYPE, gridArea: { type: GRID_AREA_TYPE } }, + { type: GRID_ITEM_TYPE, gridArea: { type: GRID_AREA_TYPE } }, + ], + }) + expect(deserialize(serialized)).toEqual(model) + }) + + it('deserializes partial or empty input into a valid empty model', () => { + expect(deserialize(undefined)).toEqual(createEmptyModel()) + expect(deserialize({})).toEqual(createEmptyModel()) + }) +}) + +describe('builder model — property setters', () => { + it('sets widget titles immutably and ignores out-of-range indexes', () => { + const model = addWidget(createEmptyModel(), textBlock) + const original = clone(model) + const result = setWidgetTitle(model, 0, 'Hero copy') + const outOfRange = setWidgetTitle(model, 99, 'Ignored') + + expect(model).toEqual(original) + expect(result).not.toBe(model) + expect(result.items[0].title).toBe('Hero copy') + expect(outOfRange).toEqual(original) + }) + + it('sets widget scope and label immutably without changing siblings', () => { + const model = addWidget( + addWidget(createEmptyModel(), textBlock), + imageBlock + ) + const original = clone(model) + const scoped = setWidgetScope(model, 0, 'person') + const labeled = setWidgetLabel(scoped, 0, 'Person details') + + expect(model).toEqual(original) + expect(scoped.items[0].viewConfig.scope).toBe('person') + expect(labeled.items[0].viewConfig.label).toBe('Person details') + expect(scoped.items[1]).toEqual(model.items[1]) + expect(labeled.items[1]).toEqual(model.items[1]) + }) + + it('sets inline recipe config values immutably and preserves existing keys', () => { + const model = addWidget(createEmptyModel(), configlessBlock) + const original = clone(model) + const originalRecipe = model.items[0].viewConfig.recipe + + const withPlaceholder = setWidgetConfigValue( + model, + 0, + 'placeholder', + 'Enter text' + ) + const withRows = setWidgetConfigValue(withPlaceholder, 0, 'rows', 3) + + expect(model).toEqual(original) + expect(getWidgetConfigValue(withPlaceholder.items[0], 'placeholder')).toBe( + 'Enter text' + ) + expect(getWidgetConfigValue(withRows.items[0], 'placeholder')).toBe( + 'Enter text' + ) + expect(getWidgetConfigValue(withRows.items[0], 'rows')).toBe(3) + expect(getWidgetConfigValue(model.items[0], 'placeholder')).toBeUndefined() + if (originalRecipe && typeof originalRecipe !== 'string') { + expect(originalRecipe.config).toBeUndefined() + expect(withPlaceholder.items[0].viewConfig.recipe).not.toBe( + originalRecipe + ) + } + }) + + it('sets and clears per-widget style values immutably', () => { + const model = addWidget(createEmptyModel(), textBlock) + const original = clone(model) + + const aligned = setWidgetStyleValue(model, 0, 'textAlign', 'center') + const sized = setWidgetStyleValue(aligned, 0, 'fontSize', '28px') + const titled = setWidgetStyleValue(sized, 0, 'bold', true, 'title') + const cleared = setWidgetStyleValue(titled, 0, 'textAlign', '') + + expect(model).toEqual(original) + expect(getWidgetStyleValue(aligned.items[0], 'textAlign')).toBe('center') + expect(getWidgetStyleValue(sized.items[0], 'fontSize')).toBe('28px') + expect(getWidgetStyleValue(titled.items[0], 'bold', 'title')).toBe(true) + expect(getWidgetStyleValue(titled.items[0], 'bold', 'body')).toBeUndefined() + expect(getWidgetStyleValue(cleared.items[0], 'textAlign')).toBeUndefined() + expect(getWidgetStyleValue(model.items[0], 'textAlign')).toBeUndefined() + }) + + it('round-trips per-widget style through serialize and deserialize', () => { + const model = setWidgetStyleValue( + addWidget(createEmptyModel(), textBlock), + 0, + 'textAlign', + 'center' + ) + const styled = setWidgetStyleValue(model, 0, 'bold', true) + const titled = setWidgetStyleValue(styled, 0, 'fontSize', '40px', 'title') + expect(deserialize(serialize(titled))).toEqual(titled) + }) + + it('does not set config values on string recipe widgets', () => { + const model = addWidget( + createEmptyModel(), + textBlock, + { rowStart: 1, rowEnd: 1, columnStart: 1, columnEnd: 1 }, + { + type: REFERENCE_VIEW_CONFIG_TYPE, + scope: '', + recipe: 'SomeRecipe', + } + ) + const original = clone(model) + const result = setWidgetConfigValue(model, 0, 'placeholder', 'Ignored') + + expect(result).toEqual(original) + expect(result.items[0].viewConfig).toBe(model.items[0].viewConfig) + expect(getWidgetConfigValue(result.items[0], 'placeholder')).toBeUndefined() + }) + + it('sets widget areas through resize normalization and clamping immutably', () => { + const model = addWidget(createEmptyModel(), textBlock) + const original = clone(model) + const result = setWidgetArea(model, 0, { + rowStart: 20, + rowEnd: 1, + columnStart: 15, + columnEnd: 1, + }) + + expect(model).toEqual(original) + expect(result.items[0].gridArea).toEqual({ + rowStart: 16, + rowEnd: 16, + columnStart: 15, + columnEnd: 15, + }) + }) + + it('returns undefined for missing config keys and string recipe widgets', () => { + const defaultModel = addWidget(createEmptyModel(), textBlock) + const referenceModel = addWidget( + createEmptyModel(), + textBlock, + { rowStart: 1, rowEnd: 1, columnStart: 1, columnEnd: 1 }, + { + type: REFERENCE_VIEW_CONFIG_TYPE, + scope: '', + recipe: 'SomeRecipe', + } + ) + + expect( + getWidgetConfigValue(defaultModel.items[0], 'missing') + ).toBeUndefined() + expect( + getWidgetConfigValue(referenceModel.items[0], 'missing') + ).toBeUndefined() + }) +}) + +describe('builder model — default config seeding', () => { + const tableBlock = getBlock('table') ?? BLOCKS[3] + + it('seeds a block default config onto a new widget', () => { + const model = addWidget(createEmptyModel(), imageBlock) + + expect(getWidgetConfigValue(model.items[0], 'fill')).toBe('width') + }) + + it('seeds table rows so the widget renders before any upload', () => { + const model = addWidget(createEmptyModel(), tableBlock) + + // The Table widget renders structured rows; a seeded grid keeps preview valid. + const rows = getWidgetConfigValue(model.items[0], 'rows') + expect(Array.isArray(rows)).toBe(true) + expect((rows as string[][])[0]).toEqual(['Column A', 'Column B']) + }) + + it('does not share seeded config between two widgets of the same block', () => { + const first = addWidget(createEmptyModel(), imageBlock) + const both = addWidget(first, imageBlock) + const updated = setWidgetConfigValue(both, 1, 'caption', 'Second') + + expect(getWidgetConfigValue(updated.items[0], 'caption')).toBeUndefined() + expect(getWidgetConfigValue(updated.items[1], 'caption')).toBe('Second') + }) +}) + +describe('builder model — insertWidgetItem', () => { + it('appends a deep-cloned copy whose nested view config is independent', () => { + const sourceModel = setWidgetConfigValue( + addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 2, + columnStart: 1, + columnEnd: 2, + }), + 0, + 'nested', + { text: 'original' } + ) + const source = sourceModel.items[0] + const result = insertWidgetItem(createEmptyModel(), source) + const inserted = result.items[0] + const sourceRecipe = source.viewConfig.recipe as any + const insertedRecipe = inserted.viewConfig.recipe as any + + expect(result.items).toHaveLength(1) + expect(inserted).not.toBe(source) + expect(inserted.viewConfig).not.toBe(source.viewConfig) + expect(insertedRecipe).not.toBe(sourceRecipe) + expect(insertedRecipe.config.nested).not.toBe(sourceRecipe.config.nested) + + sourceRecipe.config.nested.text = 'mutated source' + expect(insertedRecipe.config.nested.text).toBe('original') + + insertedRecipe.config.nested.text = 'mutated copy' + expect(sourceRecipe.config.nested.text).toBe('mutated source') + }) + + it('places the copy in free space without overlapping existing items', () => { + const model = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 2, + columnStart: 1, + columnEnd: 2, + }) + const result = insertWidgetItem(model, model.items[0]) + const inserted = result.items[result.items.length - 1] + + expect(result.items).toHaveLength(model.items.length + 1) + for (const item of model.items) { + expect(areasOverlap(item.gridArea, inserted.gridArea)).toBe(false) + } + }) + + it('grows the grid when full so the copy never overlaps', () => { + const full = addWidget(createEmptyModel(), textBlock, { + rowStart: 1, + rowEnd: 16, + columnStart: 1, + columnEnd: 24, + }) + const result = insertWidgetItem(full, full.items[0]) + + expect(result.size.rows).toBeGreaterThan(full.size.rows) + expect(result.items).toHaveLength(2) + expect( + areasOverlap(result.items[0].gridArea, result.items[1].gridArea) + ).toBe(false) + }) + + it('returns a new model without mutating the original model or items array', () => { + const model = addWidget(createEmptyModel(), textBlock) + const original = clone(model) + const originalItems = model.items + const result = insertWidgetItem(model, model.items[0]) + + expect(model).toEqual(original) + expect(model.items).toBe(originalItems) + expect(model.items).toHaveLength(1) + expect(result).not.toBe(model) + expect(result.items).not.toBe(model.items) + expect(result.items).toHaveLength(2) + }) +}) + +describe('builder model — nesting', () => { + it('identifies section widgets as containers only when their recipe is an inline grid', () => { + const sectionModel = addWidget(createEmptyModel(), sectionBlock) + const textModel = addWidget(createEmptyModel(), textBlock) + const referenceModel = addWidget( + createEmptyModel(), + textBlock, + { rowStart: 1, rowEnd: 1, columnStart: 1, columnEnd: 1 }, + { + type: REFERENCE_VIEW_CONFIG_TYPE, + scope: '', + recipe: 'X', + } + ) + + expect(isContainerItem(sectionModel.items[0])).toBe(true) + expect(isContainerItem(textModel.items[0])).toBe(false) + expect(isContainerItem(referenceModel.items[0])).toBe(false) + expect(getWidgetConfigValue(sectionModel.items[0], 'items')).toEqual([]) + }) + + it('coerces arbitrary values into valid nested builder models', () => { + const item = addWidget(createEmptyModel(), textBlock).items[0] + const items = [item] + + expect(ensureModel(undefined)).toEqual({ + size: { columns: 6, rows: 4, rowGap: '8px', columnGap: '8px' }, + items: [], + itemBorder: { size: '1px', style: 'solid', color: '#bbb', radius: '5px' }, + showItemBorders: false, + }) + expect(ensureModel({}).items).toEqual([]) + expect(ensureModel({ items }).items).toBe(items) + expect(ensureModel({ size: { rows: 9 } }).size).toEqual({ + columns: 6, + rows: 9, + rowGap: '8px', + columnGap: '8px', + }) + }) + + it('reads nested grid models by path without throwing for invalid indexes', () => { + const model = addWidget(createEmptyModel(), sectionBlock) + + expect(getSubModel(model, [])).toBe(model) + expect(getSubModel(model, [0])).toEqual({ + size: { columns: 12, rows: 8, rowGap: '8px', columnGap: '8px' }, + items: [], + itemBorder: { size: '1px', style: 'solid', color: '#bbb', radius: '5px' }, + showItemBorders: false, + }) + expect(getSubModel(model, [0, 99])).toEqual(getSubModel(model, [0])) + }) + + it('sets nested grid models immutably and no-ops for invalid paths', () => { + const model = addWidget( + addWidget(createEmptyModel(), sectionBlock), + textBlock + ) + const original = clone(model) + const sub = addWidget(getSubModel(model, [0]), imageBlock) + + expect(setSubModel(model, [], sub)).toBe(sub) + + const updated = setSubModel(model, [0], sub) + expect(model).toEqual(original) + expect(updated).not.toBe(model) + expect(getSubModel(updated, [0])).toEqual(sub) + expect(setSubModel(model, [1], sub)).toEqual(original) + expect(setSubModel(model, [99], sub)).toEqual(original) + }) + + it('round-trips adding a widget inside a section while preserving the root section', () => { + const sectionIndex = 0 + const model = addWidget(createEmptyModel(), sectionBlock) + const child = addWidget(getSubModel(model, [sectionIndex]), textBlock) + const updated = setSubModel(model, [sectionIndex], child) + + expect(getSubModel(updated, [sectionIndex]).items).toHaveLength(1) + expect(updated.items[sectionIndex].title).toBe(sectionBlock.label) + expect(updated.items).toHaveLength(1) + }) + + it('recursively serializes nested section grids with type discriminators', () => { + const model = setSubModel( + addWidget(createEmptyModel(), sectionBlock), + [0], + addWidget( + getSubModel(addWidget(createEmptyModel(), sectionBlock), [0]), + textBlock + ) + ) + const nested = (serialize(model).items as any[])[0].viewConfig.recipe.config + + expect(nested.type).toBe(GRID_CONFIG_TYPE) + expect(nested.size.type).toBe(GRID_SIZE_TYPE) + expect(nested.items[0].type).toBe(GRID_ITEM_TYPE) + }) + + it('round-trips a nested section through serialize and deserialize', () => { + const model = setSubModel( + addWidget(createEmptyModel(), sectionBlock), + [0], + addWidget( + getSubModel(addWidget(createEmptyModel(), sectionBlock), [0]), + textBlock + ) + ) + + expect(deserialize(serialize(model))).toEqual(model) + }) +}) + +describe('builder model — rescaleGrid', () => { + it('doubles density and scales widget areas while keeping the model immutable', () => { + const model = addWidget( + createEmptyModel({ columns: 12, rows: 8 }), + textBlock, + { + rowStart: 1, + rowEnd: 4, + columnStart: 3, + columnEnd: 6, + } + ) + const original = clone(model) + const result = rescaleGrid(model, 2) + + expect(model).toEqual(original) + expect(result.size.columns).toBe(24) + expect(result.size.rows).toBe(16) + expect(result.items[0].gridArea).toEqual({ + columnStart: 5, + columnEnd: 12, + rowStart: 1, + rowEnd: 8, + }) + }) + + it('returns the same model for a factor of 1 or non-positive factors', () => { + const model = addWidget(createEmptyModel(), textBlock) + expect(rescaleGrid(model, 1)).toBe(model) + expect(rescaleGrid(model, 0)).toBe(model) + expect(rescaleGrid(model, -2)).toBe(model) + }) + + it('clamps density to the maximum and never drops a widget below 1x1', () => { + const model = addWidget( + createEmptyModel({ columns: 200, rows: 200 }), + textBlock, + { + rowStart: 1, + rowEnd: 1, + columnStart: 1, + columnEnd: 1, + } + ) + const result = rescaleGrid(model, 4) + + expect(result.size.columns).toBeLessThanOrEqual(240) + expect(result.size.rows).toBeLessThanOrEqual(240) + const { gridArea } = result.items[0] + expect(gridArea.columnEnd).toBeGreaterThanOrEqual(gridArea.columnStart) + expect(gridArea.rowEnd).toBeGreaterThanOrEqual(gridArea.rowStart) + }) + + it('keeps every scaled widget inside the new grid bounds', () => { + const model = addWidget( + createEmptyModel({ columns: 12, rows: 8 }), + textBlock, + { + rowStart: 7, + rowEnd: 8, + columnStart: 11, + columnEnd: 12, + } + ) + const result = rescaleGrid(model, 0.5) + + expect(result.size.columns).toBe(6) + expect(result.size.rows).toBe(4) + const { gridArea } = result.items[0] + expect(gridArea.columnEnd).toBeLessThanOrEqual(6) + expect(gridArea.rowEnd).toBeLessThanOrEqual(4) + }) +}) + +describe('builder model — clampPath', () => { + it('keeps a valid path that resolves through container items', () => { + const model = addWidget(createEmptyModel(), sectionBlock) + expect(clampPath(model, [])).toEqual([]) + expect(clampPath(model, [0])).toEqual([0]) + }) + + it('truncates at a non-container item', () => { + const model = addWidget(createEmptyModel(), textBlock) + expect(clampPath(model, [0])).toEqual([]) + }) + + it('truncates at a missing index', () => { + const model = addWidget(createEmptyModel(), sectionBlock) + expect(clampPath(model, [0, 5])).toEqual([0]) + expect(clampPath(model, [9])).toEqual([]) + }) + + it('truncates a deep path when an ancestor section is removed', () => { + const withSection = addWidget(createEmptyModel(), sectionBlock) + const nested = addWidget(getSubModel(withSection, [0]), sectionBlock) + const model = setSubModel(withSection, [0], nested) + + expect(clampPath(model, [0, 0])).toEqual([0, 0]) + // After removing the outer section the whole path is invalid. + expect(clampPath(removeWidget(model, 0), [0, 0])).toEqual([]) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/registry.test.ts b/packages/dm-core-plugins/src/builder/tests/registry.test.ts new file mode 100644 index 000000000..4cfd92ae9 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/registry.test.ts @@ -0,0 +1,49 @@ +import { ICONS } from '../utils/icons' +import { + BLOCKS, + builderStaticPlugins, + getBlock, + WIDGET_DEFINITIONS, +} from '../widgets' + +/** + * Guardrails for the widget registry. These fail with a clear message when a + * newly added widget is misconfigured, so you don't have to hunt through the + * running app to find the mistake. See builder/docs/ADDING_WIDGETS.md. + */ +describe('widget registry', () => { + it('gives every widget a unique block id', () => { + const ids = WIDGET_DEFINITIONS.map((definition) => definition.block.id) + expect(new Set(ids).size).toBe(ids.length) + }) + + it('gives every widget a unique recipe', () => { + const recipes = WIDGET_DEFINITIONS.map( + (definition) => definition.block.recipe + ) + expect(new Set(recipes).size).toBe(recipes.length) + }) + + it('exposes each definition through BLOCKS and getBlock', () => { + expect(BLOCKS).toHaveLength(WIDGET_DEFINITIONS.length) + for (const definition of WIDGET_DEFINITIONS) { + expect(getBlock(definition.block.id)).toBe(definition.block) + } + }) + + it('resolves every block icon to icon data', () => { + for (const definition of WIDGET_DEFINITIONS) { + expect(ICONS[definition.block.icon]).toBeDefined() + } + }) + + it('registers a runtime component for every static widget (and only those)', () => { + const staticWidgets = WIDGET_DEFINITIONS.filter( + (definition) => definition.load + ) + expect(Object.keys(builderStaticPlugins)).toHaveLength(staticWidgets.length) + for (const definition of staticWidgets) { + expect(builderStaticPlugins[definition.block.recipe]).toBeDefined() + } + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/site.test.ts b/packages/dm-core-plugins/src/builder/tests/site.test.ts new file mode 100644 index 000000000..72f1cc57c --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/site.test.ts @@ -0,0 +1,453 @@ +import { BLOCKS, getBlock } from '../model/blocks' +import { addWidget, createEmptyModel, serialize } from '../model/model' +import { + addNavItem, + addPage, + createDefaultNavbar, + createEmptySite, + createPage, + deserializeSite, + findPage, + findPageContext, + findPageIndex, + findPagePath, + isSerializedSite, + moveNavItem, + movePage, + NAVBAR_ITEM_TYPE, + NAVBAR_TYPE, + PAGE_TYPE, + removeNavItem, + removePage, + renamePage, + resolvePluginAliases, + SITE_TYPE, + SITE_TYPE_ADDRESS, + serializeSite, + setPageLayout, + stampWidgetConfigTypes, + type TBuilderSite, + updateNavbar, + updateNavItem, + WIDGET_CONFIG_TYPE, +} from '../model/site' + +const textBlock = getBlock('text') ?? BLOCKS[0] +const clone = (value: T): T => JSON.parse(JSON.stringify(value)) + +describe('builder site', () => { + it('creates an empty site with a single Home page', () => { + const site = createEmptySite() + expect(site.pages).toHaveLength(1) + expect(site.pages[0].title).toBe('Home') + expect(site.pages[0].layout.items).toEqual([]) + expect(site.pages[0].id).toEqual(expect.any(String)) + }) + + it('round-trips through serialize and deserialize with type discriminators', () => { + const site: TBuilderSite = { + title: 'My Site', + published: true, + navbar: createDefaultNavbar(), + pages: [ + createPage('Home', addWidget(createEmptyModel(), textBlock)), + createPage('About'), + ], + } + const serialized = serializeSite(site) + + expect(serialized.type).toBe(SITE_TYPE) + expect(serialized.title).toBe('My Site') + expect(serialized.published).toBe(true) + const pages = serialized.pages as Array> + expect(pages).toHaveLength(2) + expect(pages[0].type).toBe(PAGE_TYPE) + expect(pages[0].title).toBe('Home') + + const restored = deserializeSite(serialized) + expect(restored.pages.map((page) => page.title)).toEqual(['Home', 'About']) + expect(restored.pages[0].layout.items).toHaveLength(1) + expect(restored.title).toBe('My Site') + expect(restored.published).toBe(true) + }) + + it('reads site metadata with slug fallback and draft default', () => { + // Title falls back to the document `name` (slug) when unset; a site without + // an explicit `published` flag is treated as an unpublished draft. + const fallback = deserializeSite({ + type: SITE_TYPE, + name: 'My_Company', + pages: [{ type: PAGE_TYPE, id: 'p1', title: 'Home' }], + }) + expect(fallback.title).toBe('My_Company') + expect(fallback.published).toBe(false) + }) + + it('wraps a legacy single-grid layout into a one-page Home site', () => { + const grid = serialize(addWidget(createEmptyModel(), textBlock)) + expect(isSerializedSite(grid)).toBe(false) + + const site = deserializeSite(grid) + expect(site.pages).toHaveLength(1) + expect(site.pages[0].title).toBe('Home') + expect(site.pages[0].layout.items).toHaveLength(1) + }) + + it('falls back to an empty site for empty or invalid input', () => { + expect(deserializeSite(undefined).pages).toHaveLength(1) + expect(deserializeSite(null).pages).toHaveLength(1) + expect(deserializeSite({ pages: [] }).pages).toHaveLength(1) + }) + + it('never throws and always yields at least one page on corrupt input', () => { + const corrupt: unknown[] = [ + 42, + 'a string layout', + true, + [], + { pages: 'not-an-array' }, + { pages: [null, 1, 'x', {}] }, + { pages: [{ id: 5, title: {}, layout: 'oops', children: 'nope' }] }, + { pages: [{ layout: { items: 42, size: 7 } }] }, + { navbar: 'broken', pages: [{ title: 'Home' }] }, + { pages: [{ children: [{ children: [{ title: 3 }] }] }] }, + ] + for (const input of corrupt) { + expect(() => deserializeSite(input)).not.toThrow() + const site = deserializeSite(input) + expect(site.pages.length).toBeGreaterThanOrEqual(1) + expect(site.navbar).toBeDefined() + } + }) + + it('adds a page with a unique default title without mutating the original', () => { + const site = createEmptySite() + const original = clone(site) + const { site: next, id } = addPage(site) + + expect(site).toEqual(original) + expect(next.pages).toHaveLength(2) + expect(findPageIndex(next, id)).toBe(1) + expect(next.pages[1].title).toBe('Page 2') + }) + + it('renames a page immutably and ignores unknown ids', () => { + const site = createEmptySite() + const id = site.pages[0].id + const renamed = renamePage(site, id, 'Landing') + + expect(renamed.pages[0].title).toBe('Landing') + expect(site.pages[0].title).toBe('Home') + expect(renamePage(site, 'missing', 'X')).toBe(site) + }) + + it('removes a page but never the last one', () => { + const { site: two } = addPage(createEmptySite()) + const firstId = two.pages[0].id + const reduced = removePage(two, firstId) + + expect(reduced.pages).toHaveLength(1) + expect(reduced.pages[0].id).toBe(two.pages[1].id) + expect(removePage(reduced, reduced.pages[0].id)).toBe(reduced) + }) + + it('replaces a page layout immutably and no-ops for identical layouts', () => { + const site = createEmptySite() + const id = site.pages[0].id + const layout = addWidget(createEmptyModel(), textBlock) + const updated = setPageLayout(site, id, layout) + + expect(updated.pages[0].layout).toBe(layout) + expect(site.pages[0].layout.items).toEqual([]) + expect(setPageLayout(updated, id, layout)).toBe(updated) + expect(setPageLayout(site, 'missing', layout)).toBe(site) + }) + + it('reorders pages and clamps out-of-range targets', () => { + let site = createEmptySite() + site = addPage(site).site + site = addPage(site).site + const titles = site.pages.map((page) => page.title) + + const moved = movePage(site, null, 0, 2) + expect(moved.pages.map((page) => page.title)).toEqual([ + titles[1], + titles[2], + titles[0], + ]) + expect(movePage(site, null, 0, 99).pages[2].title).toBe(titles[0]) + expect(movePage(site, null, 1, 1)).toBe(site) + expect(movePage(site, null, 5, 0)).toBe(site) + }) + + it('adds, finds, and serializes nested sub-pages to any depth', () => { + const base = createEmptySite() + const homeId = base.pages[0].id + const { site: withChild, id: childId } = addPage(base, homeId) + const { site: withGrandchild, id: grandchildId } = addPage( + withChild, + childId + ) + + expect(withChild.pages[0].children).toHaveLength(1) + expect(withChild.pages[0].children[0].title).toBe('Page 1') + + const context = findPageContext(withGrandchild, grandchildId) + expect(context?.parentId).toBe(childId) + expect(findPage(withGrandchild, grandchildId)?.id).toBe(grandchildId) + expect(findPagePath(withGrandchild, grandchildId).map((p) => p.id)).toEqual( + [homeId, childId, grandchildId] + ) + + const restored = deserializeSite(serializeSite(withGrandchild)) + expect(restored.pages[0].children[0].children[0].id).toBe(grandchildId) + }) + + it('renames and removes nested pages without touching siblings', () => { + const base = createEmptySite() + const homeId = base.pages[0].id + const { site: a, id: childAId } = addPage(base, homeId) + const { site: site2, id: childBId } = addPage(a, homeId) + + const renamed = renamePage(site2, childBId, 'Contact') + expect(renamed.pages[0].children.map((p) => p.title)).toEqual([ + 'Page 1', + 'Contact', + ]) + + const removed = removePage(renamed, childAId) + expect(removed.pages[0].children).toHaveLength(1) + expect(removed.pages[0].children[0].id).toBe(childBId) + }) + + it('reorders sub-pages within their parent', () => { + const base = createEmptySite() + const homeId = base.pages[0].id + const { site: a } = addPage(base, homeId) + const { site: site2 } = addPage(a, homeId) + const childTitles = site2.pages[0].children.map((p) => p.title) + + const moved = movePage(site2, homeId, 0, 1) + expect(moved.pages[0].children.map((p) => p.title)).toEqual([ + childTitles[1], + childTitles[0], + ]) + }) + + it('keeps children as an empty array for legacy pages without children', () => { + const legacy = { + type: 'PLUGINS:dm-core-plugins/builder/Site', + pages: [ + { id: 'x', title: 'Home', layout: serialize(createEmptyModel()) }, + ], + } + const site = deserializeSite(legacy) + expect(site.pages[0].children).toEqual([]) + }) + + describe('navbar', () => { + it('creates a disabled default navbar on a new site', () => { + const site = createEmptySite() + expect(site.navbar.enabled).toBe(false) + expect(site.navbar.items).toEqual([]) + expect(site.navbar.align).toBe('right') + }) + + it('updates navbar fields immutably', () => { + const site = createEmptySite() + const updated = updateNavbar(site, { enabled: true, brand: 'Acme' }) + + expect(updated.navbar.enabled).toBe(true) + expect(updated.navbar.brand).toBe('Acme') + expect(site.navbar.enabled).toBe(false) + expect(site.navbar.brand).toBe('My Site') + }) + + it('adds a nav item defaulting to the first page', () => { + const site = createEmptySite() + const homeId = site.pages[0].id + const { site: next, id } = addNavItem(site) + + expect(next.navbar.items).toHaveLength(1) + expect(next.navbar.items[0].id).toBe(id) + expect(next.navbar.items[0].label).toBe('Home') + expect(next.navbar.items[0].target).toEqual({ + kind: 'page', + pageId: homeId, + }) + expect(site.navbar.items).toHaveLength(0) + }) + + it('adds a nav item with an explicit URL target', () => { + const site = createEmptySite() + const { site: next, id } = addNavItem(site, { + kind: 'url', + href: 'https://example.com', + }) + + expect(next.navbar.items[0].id).toBe(id) + expect(next.navbar.items[0].target).toEqual({ + kind: 'url', + href: 'https://example.com', + }) + }) + + it('updates and removes nav items immutably and ignores unknown ids', () => { + const { site: withItem, id } = addNavItem(createEmptySite()) + const renamed = updateNavItem(withItem, id, { label: 'Docs' }) + + expect(renamed.navbar.items[0].label).toBe('Docs') + expect(withItem.navbar.items[0].label).toBe('Home') + expect(updateNavItem(withItem, 'missing', { label: 'X' })).toBe(withItem) + + const removed = removeNavItem(renamed, id) + expect(removed.navbar.items).toHaveLength(0) + expect(removeNavItem(renamed, 'missing')).toBe(renamed) + }) + + it('reorders nav items and clamps out-of-range targets', () => { + let site = createEmptySite() + site = addNavItem(site, { kind: 'url', href: 'a' }).site + site = addNavItem(site, { kind: 'url', href: 'b' }).site + site = addNavItem(site, { kind: 'url', href: 'c' }).site + const hrefs = site.navbar.items.map( + (item) => (item.target as { href: string }).href + ) + + const moved = moveNavItem(site, 0, 2) + expect( + moved.navbar.items.map((item) => (item.target as { href: string }).href) + ).toEqual([hrefs[1], hrefs[2], hrefs[0]]) + expect(moveNavItem(site, 1, 1)).toBe(site) + expect(moveNavItem(site, 5, 0)).toBe(site) + }) + + it('round-trips the navbar through serialize and deserialize', () => { + let site = updateNavbar(createEmptySite(), { + enabled: true, + brand: 'Acme', + align: 'center', + background: '#000000', + }) + site = addNavItem(site).site + site = addNavItem(site, { kind: 'url', href: 'https://x.dev' }).site + + const serialized = serializeSite(site) + const navbar = serialized.navbar as Record + expect(navbar.type).toBe(NAVBAR_TYPE) + const items = navbar.items as Array> + expect(items[0].type).toBe(NAVBAR_ITEM_TYPE) + + const restored = deserializeSite(serialized) + expect(restored.navbar.enabled).toBe(true) + expect(restored.navbar.brand).toBe('Acme') + expect(restored.navbar.align).toBe('center') + expect(restored.navbar.items).toHaveLength(2) + expect(restored.navbar.items[1].target).toEqual({ + kind: 'url', + href: 'https://x.dev', + }) + }) + + it('defaults the navbar when missing from stored JSON (backward compat)', () => { + const legacy = { + type: 'PLUGINS:dm-core-plugins/builder/Site', + pages: [ + { id: 'x', title: 'Home', layout: serialize(createEmptyModel()) }, + ], + } + const site = deserializeSite(legacy) + expect(site.navbar).toEqual(createDefaultNavbar()) + }) + }) + + describe('plugin alias resolution', () => { + it('exposes the Site type as a fully-qualified dmss address', () => { + expect(SITE_TYPE).toBe('PLUGINS:dm-core-plugins/builder/Site') + expect(SITE_TYPE_ADDRESS).toBe( + 'dmss://system/Plugins/dm-core-plugins/builder/Site' + ) + }) + + it('rewrites every PLUGINS: alias to its dmss address, deeply', () => { + const serialized = serializeSite(createEmptySite()) + const resolved = resolvePluginAliases(serialized) + const asText = JSON.stringify(resolved) + expect(asText).not.toContain('PLUGINS:') + expect(resolved.type).toBe(SITE_TYPE_ADDRESS) + // Nested page/navbar discriminators are rewritten too. + expect(asText).toContain('dmss://system/Plugins/dm-core-plugins/builder/') + }) + + it('still deserializes a site whose types are fully-qualified addresses', () => { + const resolved = resolvePluginAliases(serializeSite(createEmptySite())) + expect(isSerializedSite(resolved)).toBe(true) + const site = deserializeSite(resolved) + expect(site.pages).toHaveLength(1) + expect(site.pages[0].title).toBe('Home') + }) + }) + + describe('widget config type stamping', () => { + it('stamps a resolvable type onto every typeless config, in place', () => { + const doc = { + recipe: { config: { text: 'Hi', level: 1 } }, + nested: [{ recipe: { config: { label: 'Go' } } }], + } + stampWidgetConfigTypes(doc) + expect(doc.recipe.config).toMatchObject({ + text: 'Hi', + level: 1, + type: WIDGET_CONFIG_TYPE, + }) + expect(doc.nested[0].recipe.config).toMatchObject({ + label: 'Go', + type: WIDGET_CONFIG_TYPE, + }) + }) + + it('leaves configs that already declare a type untouched (idempotent)', () => { + const doc = { config: { text: 'Hi', type: 'dmss://custom/Type' } } + stampWidgetConfigTypes(doc) + expect(doc.config.type).toBe('dmss://custom/Type') + }) + + it('does not recurse into config values (e.g. a chart rows array)', () => { + const doc: { config: Record } = { + config: { + chartType: 'line', + rows: [ + ['Month', 'Sales'], + ['Jan', '12'], + ], + }, + } + stampWidgetConfigTypes(doc) + expect(doc.config.type).toBe(WIDGET_CONFIG_TYPE) + // Values inside config keep their shape and gain no stray `type`. + expect(doc.config.rows).toEqual([ + ['Month', 'Sales'], + ['Jan', '12'], + ]) + }) + + it('resolvePluginAliases stamps widget configs while expanding aliases', () => { + const serialized = { + type: SITE_TYPE, + pages: [ + { layout: { items: [{ recipe: { config: { text: 'Hi' } } }] } }, + ], + } + const resolved = resolvePluginAliases(serialized) as { + type: string + pages: { + layout: { items: { recipe: { config: Record } }[] } + }[] + } + expect(resolved.type).toBe(SITE_TYPE_ADDRESS) + expect(resolved.pages[0].layout.items[0].recipe.config.type).toBe( + WIDGET_CONFIG_TYPE + ) + }) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/siteBlueprints.test.ts b/packages/dm-core-plugins/src/builder/tests/siteBlueprints.test.ts new file mode 100644 index 000000000..72bf0a3ed --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/siteBlueprints.test.ts @@ -0,0 +1,156 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' +import { BLOCKS, getBlock } from '../model/blocks' +import { addWidget, createEmptyModel } from '../model/model' +import { + addNavItem, + addPage, + createDefaultNavbar, + createPage, + deserializeSite, + NAVBAR_ITEM_TYPE, + NAVBAR_TYPE, + PAGE_TYPE, + SITE_SCHEMA_VERSION, + SITE_TYPE, + serializeSite, + type TBuilderSite, +} from '../model/site' + +type TBlueprintAttribute = { + name: string + attributeType: string + optional?: boolean + dimensions?: string +} + +type TBlueprint = { + name: string + attributes: TBlueprintAttribute[] +} + +const BLUEPRINT_DIR = join(__dirname, '..', '..', '..', 'blueprints', 'builder') + +const loadBlueprint = (name: string): TBlueprint => + JSON.parse( + readFileSync(join(BLUEPRINT_DIR, `${name}.json`), 'utf-8') + ) as TBlueprint + +const blueprints: Record = { + [SITE_TYPE]: loadBlueprint('Site'), + [PAGE_TYPE]: loadBlueprint('Page'), + [NAVBAR_TYPE]: loadBlueprint('Navbar'), + [NAVBAR_ITEM_TYPE]: loadBlueprint('NavbarItem'), +} + +/** + * Structurally validate a serialized node against its builder blueprint: + * required attributes present, no unknown keys, correct `type` discriminator, + * recursing through the builder-owned children (but not the opaque grid layout, + * which the grid plugin's own blueprints cover). + */ +const validateNode = (node: unknown, expectedType: string, path: string) => { + expect(typeof node).toBe('object') + expect(node).not.toBeNull() + const obj = node as Record + + expect(obj.type).toBe(expectedType) + + const blueprint = blueprints[expectedType] + expect(blueprint).toBeDefined() + + const names = new Set(blueprint.attributes.map((attr) => attr.name)) + + for (const attr of blueprint.attributes) { + if (!attr.optional) { + expect(obj[attr.name]).toBeDefined() + } + } + + for (const key of Object.keys(obj)) { + if (!names.has(key)) { + throw new Error( + `${path}: unexpected key "${key}" not in ${blueprint.name}` + ) + } + } + + if (expectedType === SITE_TYPE) { + if (obj.navbar !== undefined) { + validateNode(obj.navbar, NAVBAR_TYPE, `${path}.navbar`) + } + for (const [i, page] of (obj.pages as unknown[]).entries()) { + validateNode(page, PAGE_TYPE, `${path}.pages[${i}]`) + } + } + + if (expectedType === PAGE_TYPE && Array.isArray(obj.children)) { + for (const [i, child] of obj.children.entries()) { + validateNode(child, PAGE_TYPE, `${path}.children[${i}]`) + } + } + + if (expectedType === NAVBAR_TYPE && Array.isArray(obj.items)) { + for (const [i, item] of obj.items.entries()) { + validateNode(item, NAVBAR_ITEM_TYPE, `${path}.items[${i}]`) + } + } +} + +/** Build a representative site exercising nesting, both nav target kinds, etc. */ +const buildRichSite = (): TBuilderSite => { + const textBlock = getBlock('text') ?? BLOCKS[0] + let site: TBuilderSite = { + title: 'Rich Site', + published: true, + navbar: { ...createDefaultNavbar(), enabled: true }, + pages: [ + createPage('Home', addWidget(createEmptyModel(), textBlock)), + createPage('About'), + ], + } + // A nested sub-page under Home. + site = addPage(site, site.pages[0].id, 'Team').site + // One page-target nav item and one url-target nav item. + site = addNavItem(site, { kind: 'page', pageId: site.pages[0].id }).site + site = addNavItem(site, { kind: 'url', href: 'https://example.com' }).site + return site +} + +describe('builder site — blueprint conformance', () => { + it('serializes a site that conforms to the Site/Page/Navbar/NavbarItem blueprints', () => { + const serialized = serializeSite(buildRichSite()) + validateNode(serialized, SITE_TYPE, 'site') + }) + + it('emits the current schemaVersion', () => { + const serialized = serializeSite(buildRichSite()) + expect(serialized.schemaVersion).toBe(SITE_SCHEMA_VERSION) + }) + + it('round-trips a rich site without losing pages, nesting or nav targets', () => { + const original = buildRichSite() + const restored = deserializeSite(serializeSite(original)) + + expect(restored.pages.map((page) => page.title)).toEqual(['Home', 'About']) + expect(restored.pages[0].children.map((child) => child.title)).toEqual([ + 'Team', + ]) + expect(restored.navbar.enabled).toBe(true) + expect(restored.navbar.items.map((item) => item.target.kind)).toEqual([ + 'page', + 'url', + ]) + }) + + it('defaults a missing schemaVersion to 1 and still loads', () => { + const serialized = serializeSite(buildRichSite()) + // Simulate a site saved before schemaVersion existed. + const { schemaVersion: _omitted, ...legacy } = serialized as Record< + string, + unknown + > + const restored = deserializeSite(legacy) + expect(restored.pages).toHaveLength(2) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/tests/templates.test.ts b/packages/dm-core-plugins/src/builder/tests/templates.test.ts new file mode 100644 index 000000000..0ee3bb5ca --- /dev/null +++ b/packages/dm-core-plugins/src/builder/tests/templates.test.ts @@ -0,0 +1,49 @@ +import { areasOverlap } from '../model/model' +import { getTemplate, TEMPLATES } from '../model/templates' + +describe('builder templates', () => { + it('defines unique templates that build valid models', () => { + const ids = new Set() + + expect(TEMPLATES.length).toBeGreaterThan(0) + for (const template of TEMPLATES) { + expect(ids.has(template.id)).toBe(false) + ids.add(template.id) + expect(template.label).toBeTruthy() + + const model = template.build() + expect(model.size.columns).toBeGreaterThan(0) + expect(model.size.rows).toBeGreaterThan(0) + expect(Array.isArray(model.items)).toBe(true) + } + }) + + it('looks up templates by id', () => { + expect(getTemplate('landing')).toBeDefined() + expect(getTemplate('does-not-exist')).toBeUndefined() + }) + + it('builds blank and dashboard templates with expected top-level layouts', () => { + const blank = getTemplate('blank')?.build() + const dashboard = getTemplate('dashboard')?.build() + + expect(blank?.items).toHaveLength(0) + expect(dashboard?.items.length).toBeGreaterThan(0) + + const items = dashboard?.items ?? [] + for (let i = 0; i < items.length; i++) { + for (let j = i + 1; j < items.length; j++) { + expect(areasOverlap(items[i].gridArea, items[j].gridArea)).toBe(false) + } + } + }) + + it('builds a fresh model each time', () => { + const template = getTemplate('article') + const first = template?.build() + const second = template?.build() + + expect(first).toEqual(second) + expect(first).not.toBe(second) + }) +}) diff --git a/packages/dm-core-plugins/src/builder/types/index.ts b/packages/dm-core-plugins/src/builder/types/index.ts new file mode 100644 index 000000000..9121c0274 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/types/index.ts @@ -0,0 +1,115 @@ +import type { TGridArea, TGridPluginConfig } from '../../grid' + +/** + * Editor model for the website builder. + * + * The model is intentionally the same shape the runtime `grid` plugin consumes + * (`TGridPluginConfig`), so "build format == runtime format": anything authored + * in the builder renders directly with the existing grid renderer. + */ +export type TBuilderModel = TGridPluginConfig + +export type TBuilderMode = 'edit' | 'preview' + +export type TBuilderPluginConfig = { + /** Initial mode the plugin opens in. Defaults to `edit`. */ + mode?: TBuilderMode + /** Optional seed grid config to hydrate the canvas with. */ + initialConfig?: Partial + /** + * When true the plugin renders as a read-only site viewer: the published + * navbar, page navigation and content only, with no editing toolbar, + * palette or inspector. Use this to mount a built site for end users who + * should be able to browse but not edit it. + */ + readOnly?: boolean + /** + * Role names allowed to edit. When set, users whose current role is not in + * this list get the read-only viewer regardless of `readOnly`. An empty or + * absent list leaves editing open to everyone. + */ + editorRoles?: string[] + /** + * URL to navigate to when the user clicks the "Website builder" breadcrumb + * in the toolbar (e.g. the site directory page). Falls back to + * `window.history.back()` when omitted. + */ + directoryUrl?: string +} + +/** + * Categories used to group blocks in the widget palette. + */ +export type TBlockCategory = 'layout' | 'content' | 'media' | 'data' + +export type TInspectorFieldType = + | 'text' + | 'textarea' + | 'number' + | 'boolean' + | 'select' + | 'image-upload' + | 'table-source' + +/** + * Where an inspector field's value is read from / written to on a grid item. + * + * - `scope`: the widget's data binding (`viewConfig.scope`). + * - `label`: the widget's display label (`viewConfig.label`). + * - `config`: a key on the inline recipe config (`viewConfig.recipe.config[key]`). + */ +export type TInspectorFieldTarget = + | { kind: 'scope' } + | { kind: 'label' } + | { kind: 'config'; key: string } + +/** + * A single editable property shown in the inspector for a block. Lets users + * configure widgets through typed form controls instead of raw JSON. + */ +export type TInspectorField = { + label: string + type: TInspectorFieldType + target: TInspectorFieldTarget + placeholder?: string + help?: string + /** Options for `select` fields. The stored value is the option's `value`. */ + options?: { label: string; value: string | number }[] +} + +/** + * A "block" is a draggable widget definition shown in the palette. Dropping a + * block onto the canvas produces a grid item in the model. + */ +export type TBlock = { + /** Stable identifier, also used as the dnd-kit draggable id in the palette. */ + id: string + /** Friendly, human-readable name shown on the palette card. */ + label: string + /** EDS icon name for the palette card. */ + icon: string + category: TBlockCategory + /** Short helper text describing what the block does. */ + description: string + /** + * Whether the block carries its own content (`content`) or binds to an + * existing DMSS entity (`data`). Drives the inspector behaviour in Phase 2. + */ + contentModel: 'content' | 'data' + /** Default footprint (in grid cells) when first dropped. */ + defaultSize: { columns: number; rows: number } + /** UI recipe / plugin this block renders with at runtime. */ + recipe: string + /** Sensible default recipe config so the widget renders without setup. */ + defaultConfig?: Record + /** + * Self-contained widgets (heading, button, divider…) render their own + * content, so they should not receive the auto-generated title that data + * widgets get. A redundant title also overflows small cells. + */ + hideTitle?: boolean + /** Block-specific properties surfaced in the inspector. */ + fields?: TInspectorField[] +} + +export type { TGridArea } diff --git a/packages/dm-core-plugins/src/builder/types/siteDirectory.ts b/packages/dm-core-plugins/src/builder/types/siteDirectory.ts new file mode 100644 index 000000000..4ca13b926 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/types/siteDirectory.ts @@ -0,0 +1,46 @@ +/** + * Configuration for the Site directory plugin. + * + * Every field is optional so the plugin works with an empty config: it then + * lists sites across the app's visible data sources and (if one is available) + * lets users create a new site in the first of them. + */ +export type TSiteDirectoryConfig = { + /** Heading shown above the gallery. Defaults to "Websites". */ + heading?: string + /** + * The entity `type` to list, as a fully-qualified `dmss://…` address (the + * search endpoint cannot resolve the `PLUGINS:` alias). Defaults to the + * builder's Site type (`dmss://system/Plugins/dm-core-plugins/builder/Site`). + * Override to list a project-specific blueprint that extends it. + */ + entityType?: string + /** + * Data sources to search. Defaults to the application's visible data sources. + */ + dataSources?: string[] + /** + * Where "New site" creates the entity — a DMSS address (usually a data source + * root, e.g. `Sites`). Defaults to the first searched data source. When no + * target can be determined, the create button is hidden. + */ + targetDataSource?: string + /** + * URL opened when a site card (or a freshly created site) is visited. `{id}` + * and `{dataSource}` are substituted. Defaults to + * `/view?documentId=dmss://{dataSource}/${id}` — the convention used by the + * example app's viewer route. The `$` marks a root document id. + */ + viewUrlTemplate?: string + /** + * URL opened right after "New site" creates a site. Because new sites are + * empty, this usually points at the editor (e.g. append `&recipe=Edit`). + * `{id}` and `{dataSource}` are substituted. Falls back to `viewUrlTemplate` + * when omitted. + */ + newSiteUrlTemplate?: string + /** Name given to a site created via "New site". Defaults to "New_site". */ + newSiteName?: string + /** Set false to hide the "New site" button (a read-only gallery). */ + allowCreate?: boolean +} diff --git a/packages/dm-core-plugins/src/builder/utils/gridMetrics.ts b/packages/dm-core-plugins/src/builder/utils/gridMetrics.ts new file mode 100644 index 000000000..4b9476639 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/gridMetrics.ts @@ -0,0 +1,40 @@ +import type { TGridSize } from '../../grid' + +/** Padding (px) inside the canvas grid; mirrors `CanvasGrid` in styles.ts. */ +export const CANVAS_GRID_PADDING = 4 + +export type TCellDelta = { columns: number; rows: number } + +/** + * Convert a pixel delta (e.g. from a dnd-kit drag or a pointer resize) into a + * whole number of grid cells, based on the measured grid rect and its size. + * + * The canvas grid uses equal `1fr` tracks with a fixed inner padding and a + * per-axis gap (`columnGap` horizontally, `rowGap` vertically), so the stride + * between adjacent cell origins is `(innerLength + gap) / tracks`. + * + * Note: gaps are assumed to be px values; non-px CSS units cannot be resolved + * from the model alone and fall back to 0. + */ +export const pixelDeltaToCells = ( + delta: { x: number; y: number }, + rect: { width: number; height: number }, + size: TGridSize, + padding: number = CANVAS_GRID_PADDING +): TCellDelta => { + const gapX = Number.parseFloat(size.columnGap) || 0 + const gapY = Number.parseFloat(size.rowGap) || 0 + const innerWidth = rect.width - padding * 2 + const innerHeight = rect.height - padding * 2 + const strideX = (innerWidth + gapX) / size.columns + const strideY = (innerHeight + gapY) / size.rows + const roundDelta = (value: number, stride: number): number => { + if (stride <= 0) return 0 + const rounded = Math.round(value / stride) + return Object.is(rounded, -0) ? 0 : rounded + } + return { + columns: roundDelta(delta.x, strideX), + rows: roundDelta(delta.y, strideY), + } +} diff --git a/packages/dm-core-plugins/src/builder/utils/icons.ts b/packages/dm-core-plugins/src/builder/utils/icons.ts new file mode 100644 index 000000000..7f43b77e1 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/icons.ts @@ -0,0 +1,68 @@ +import { + add, + check, + chevron_down, + chevron_right, + close, + copy, + delete_to_trash, + desktop_mac, + drag_handle, + edit, + external_link, + format_list_bulleted, + type IconData, + layers, + menu, + phone, + redo, + save, + settings, + tablet_android, + undo, + visibility, + visibility_off, + zoom_in, + zoom_out, +} from '@equinor/eds-icons' +import { WIDGET_ICONS } from '../widgets' + +/** + * Icons used by the builder's editor chrome (toolbar, palette, outline, nav). + * + * Widget palette icons are NOT listed here — each widget carries its own icon + * in its `*.widget.ts` file, and they are merged in via `WIDGET_ICONS`. That + * means adding a new widget never requires editing this file. + */ +const CHROME_ICONS: Record = { + add, + check, + close, + edit, + visibility, + visibility_off, + drag_handle, + delete_to_trash, + copy, + chevron_right, + chevron_down, + desktop_mac, + tablet_android, + phone, + undo, + redo, + save, + layers, + format_list_bulleted, + zoom_in, + zoom_out, + settings, + external_link, + menu, +} + +/** Maps icon names (chrome + every widget's `block.icon`) to EDS icon data. */ +export const ICONS: Record = { + ...CHROME_ICONS, + ...WIDGET_ICONS, +} diff --git a/packages/dm-core-plugins/src/builder/utils/markdownTable.ts b/packages/dm-core-plugins/src/builder/utils/markdownTable.ts new file mode 100644 index 000000000..1beefa5d5 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/markdownTable.ts @@ -0,0 +1,83 @@ +/** + * Convert between the table widget's cell grid (`string[][]`, first row = the + * header) and a GitHub-style markdown table string. This lets authors write a + * table by hand in the inspector instead of uploading a file, while the runtime + * plugin keeps consuming the same `rows` shape. + */ + +/** Escape a single cell so pipes and newlines survive the markdown encoding. */ +const escapeCell = (cell: string): string => + cell.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/\r?\n/g, ' ') + +/** Split one markdown table line into trimmed cells, honoring escaped pipes. */ +const splitRow = (line: string): string[] => { + let s = line.trim() + if (s.startsWith('|')) s = s.slice(1) + if (s.endsWith('|')) s = s.slice(0, -1) + + const cells: string[] = [] + let current = '' + for (let i = 0; i < s.length; i += 1) { + const char = s[i] + if (char === '\\' && (s[i + 1] === '|' || s[i + 1] === '\\')) { + current += s[i + 1] + i += 1 + continue + } + if (char === '|') { + cells.push(current.trim()) + current = '' + continue + } + current += char + } + cells.push(current.trim()) + return cells +} + +/** True when a line is a markdown header separator (e.g. `| --- | :--: |`). */ +const isSeparatorRow = (cells: string[]): boolean => + cells.length > 0 && cells.every((cell) => /^:?-{1,}:?$/.test(cell.trim())) + +/** + * Render a cell grid as a GitHub-style markdown table. Rows are padded to the + * widest row so the output is always well-formed; an empty grid yields an empty + * string. + */ +export const rowsToMarkdown = (rows: string[][]): string => { + if (rows.length === 0) return '' + const columnCount = Math.max(1, ...rows.map((row) => row.length)) + const pad = (row: string[]): string[] => + Array.from({ length: columnCount }, (_, index) => + escapeCell(row[index] ?? '') + ) + + const [header, ...body] = rows + const headerLine = `| ${pad(header).join(' | ')} |` + const separatorLine = `| ${Array(columnCount).fill('---').join(' | ')} |` + const bodyLines = body.map((row) => `| ${pad(row).join(' | ')} |`) + + return [headerLine, separatorLine, ...bodyLines].join('\n') +} + +/** + * Parse a GitHub-style markdown table into a cell grid (first row = header). + * The separator row is optional and ignored, surrounding pipes are optional, + * and blank lines are skipped. Returns an empty grid for empty input. + */ +export const markdownToRows = (markdown: string): string[][] => { + const lines = markdown + .split('\n') + .map((line) => line.trim()) + .filter((line) => line.length > 0) + + const rows: string[][] = [] + for (const line of lines) { + const cells = splitRow(line) + // Drop the separator, but only once a header row has been seen so a table + // that legitimately starts with dashes is not mistaken for a separator. + if (rows.length === 1 && isSeparatorRow(cells)) continue + rows.push(cells) + } + return rows +} diff --git a/packages/dm-core-plugins/src/builder/utils/mathUtils.ts b/packages/dm-core-plugins/src/builder/utils/mathUtils.ts new file mode 100644 index 000000000..4d55da0ae --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/mathUtils.ts @@ -0,0 +1,73 @@ +/** + * Small, dependency-free numeric helpers used by the builder's data widgets + * (the Metric widget and the Chart's axis scaling). Kept pure so they are easy + * to unit test and reuse. + */ + +/** Parse free-form text into a list of finite numbers, ignoring non-numbers. */ +export const parseNumbers = (text: string): number[] => { + const matches = text.match(/-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g) + if (!matches) return [] + return matches.map(Number).filter((value) => Number.isFinite(value)) +} + +export const sum = (values: number[]): number => + values.reduce((total, value) => total + value, 0) + +export const mean = (values: number[]): number => + values.length === 0 ? 0 : sum(values) / values.length + +export const min = (values: number[]): number => + values.length === 0 ? 0 : Math.min(...values) + +export const max = (values: number[]): number => + values.length === 0 ? 0 : Math.max(...values) + +export const median = (values: number[]): number => { + if (values.length === 0) return 0 + const sorted = [...values].sort((a, b) => a - b) + const mid = Math.floor(sorted.length / 2) + return sorted.length % 2 === 0 + ? (sorted[mid - 1] + sorted[mid]) / 2 + : sorted[mid] +} + +/** Population standard deviation (matches the SIMPOS report behaviour). */ +export const stddev = (values: number[]): number => { + if (values.length === 0) return 0 + const avg = mean(values) + const variance = mean(values.map((value) => (value - avg) ** 2)) + return Math.sqrt(variance) +} + +export type TAggregation = + | 'sum' + | 'mean' + | 'min' + | 'max' + | 'median' + | 'stddev' + | 'count' + +const AGGREGATIONS: Record number> = { + sum, + mean, + min, + max, + median, + stddev, + count: (values) => values.length, +} + +/** Reduce a list of numbers to a single value using the named aggregation. */ +export const aggregate = ( + values: number[], + aggregation: TAggregation +): number => (AGGREGATIONS[aggregation] ?? mean)(values) + +/** Round to a fixed number of decimals and drop trailing zeros. */ +export const formatNumber = (value: number, decimals = 2): string => { + if (!Number.isFinite(value)) return '–' + const rounded = Number(value.toFixed(Math.max(0, Math.min(10, decimals)))) + return String(rounded) +} diff --git a/packages/dm-core-plugins/src/builder/utils/spreadsheet.ts b/packages/dm-core-plugins/src/builder/utils/spreadsheet.ts new file mode 100644 index 000000000..a231e98d0 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/spreadsheet.ts @@ -0,0 +1,56 @@ +import * as XLSX from 'xlsx' + +/** Upper bounds so a huge sheet can't bloat the saved page. Pagination keeps + * rendering fast, so this can be generous. */ +export const MAX_ROWS = 5000 +export const MAX_COLS = 30 + +export type TParsedSpreadsheet = { + rows: string[][] + totalRows: number + totalCols: number + truncated: boolean +} + +/** + * Parse an uploaded CSV/TSV/Excel file into a grid of cells. The first row is + * treated as the header. Excel files are read with SheetJS; CSV/TSV are parsed + * as text so they work without loading a workbook. Output is capped to + * `MAX_ROWS`/`MAX_COLS` so a large file stays snappy; `truncated` flags when the + * cap was hit. + */ +export const parseSpreadsheet = async ( + file: File +): Promise => { + const name = file.name.toLowerCase() + let grid: string[][] + if (name.endsWith('.xlsx') || name.endsWith('.xls')) { + const buffer = await file.arrayBuffer() + const workbook = XLSX.read(buffer, { type: 'array' }) + const sheet = workbook.Sheets[workbook.SheetNames[0]] + const rows = XLSX.utils.sheet_to_json(sheet, { + header: 1, + blankrows: false, + defval: '', + }) + grid = rows.map((row) => row.map((cell) => String(cell ?? ''))) + } else { + const text = await file.text() + const separator = text.includes('\t') ? '\t' : ',' + grid = text + .replace(/\r/g, '') + .split('\n') + .filter((line) => line.trim().length > 0) + .map((line) => line.split(separator).map((cell) => cell.trim())) + } + + const totalRows = grid.length + const totalCols = grid.reduce((max, row) => Math.max(max, row.length), 0) + const rows = grid.slice(0, MAX_ROWS).map((row) => row.slice(0, MAX_COLS)) + return { + rows, + totalRows, + totalCols, + truncated: totalRows > MAX_ROWS || totalCols > MAX_COLS, + } +} diff --git a/packages/dm-core-plugins/src/builder/utils/toast.ts b/packages/dm-core-plugins/src/builder/utils/toast.ts new file mode 100644 index 000000000..1f5dd9f7c --- /dev/null +++ b/packages/dm-core-plugins/src/builder/utils/toast.ts @@ -0,0 +1,28 @@ +import { useCallback, useRef, useState } from 'react' + +export type TToast = { + /** Unique id; changing it restarts the auto-hide timer for a new message. */ + id: number + message: string +} + +export type TUseToast = { + toast: TToast | null + /** Show a transient message, replacing any currently visible one. */ + notify: (message: string) => void + dismiss: () => void +} + +/** Minimal single-slot toast state for transient builder feedback. */ +export const useToast = (): TUseToast => { + const [toast, setToast] = useState(null) + const nextId = useRef(0) + + const notify = useCallback((message: string) => { + nextId.current += 1 + setToast({ id: nextId.current, message }) + }, []) + const dismiss = useCallback(() => setToast(null), []) + + return { toast, notify, dismiss } +} diff --git a/packages/dm-core-plugins/src/builder/widgets/button.widget.ts b/packages/dm-core-plugins/src/builder/widgets/button.widget.ts new file mode 100644 index 000000000..4b40aceda --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/button.widget.ts @@ -0,0 +1,63 @@ +import { link } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Button — a self-contained call-to-action link. */ +export const buttonWidget: TWidgetDefinition = { + icon: link, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticButtonPlugin, + })), + block: { + id: 'button', + label: 'Button', + icon: 'link', + category: 'content', + description: 'A call-to-action button that links somewhere.', + contentModel: 'content', + defaultSize: { columns: 3, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-button', + defaultConfig: { label: 'Click me', variant: 'contained', align: 'left' }, + hideTitle: true, + fields: [ + { + label: 'Label', + type: 'text', + target: { kind: 'config', key: 'label' }, + help: 'Text shown on the button.', + }, + { + label: 'Link (URL)', + type: 'text', + target: { kind: 'config', key: 'href' }, + placeholder: 'https://example.com', + help: 'Where the button navigates when clicked.', + }, + { + label: 'Style', + type: 'select', + target: { kind: 'config', key: 'variant' }, + options: [ + { label: 'Filled', value: 'contained' }, + { label: 'Outlined', value: 'outlined' }, + { label: 'Text only', value: 'ghost' }, + ], + }, + { + label: 'Alignment', + type: 'select', + target: { kind: 'config', key: 'align' }, + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + { + label: 'Open in new tab', + type: 'boolean', + target: { kind: 'config', key: 'openInNewTab' }, + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/chart.widget.ts b/packages/dm-core-plugins/src/builder/widgets/chart.widget.ts new file mode 100644 index 000000000..6537ebb25 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/chart.widget.ts @@ -0,0 +1,68 @@ +import { bar_chart } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Chart — a self-contained line/bar chart drawn from an inline table. */ +export const chartWidget: TWidgetDefinition = { + icon: bar_chart, + load: () => + import('../static/StaticChartPlugin').then((m) => ({ + default: m.StaticChartPlugin, + })), + block: { + id: 'chart', + label: 'Chart', + icon: 'bar_chart', + category: 'data', + description: 'A line or bar chart drawn from a small table of numbers.', + contentModel: 'content', + defaultSize: { columns: 6, rows: 5 }, + recipe: '@development-framework/dm-core-plugins/static-chart', + defaultConfig: { + chartType: 'line', + showLegend: true, + rows: [ + ['Month', 'Sales', 'Target'], + ['Jan', '12', '10'], + ['Feb', '18', '14'], + ['Mar', '9', '16'], + ['Apr', '24', '18'], + ], + }, + hideTitle: true, + fields: [ + { + label: 'Chart title', + type: 'text', + target: { kind: 'config', key: 'title' }, + help: 'Heading shown above the chart (optional).', + }, + { + label: 'Type', + type: 'select', + target: { kind: 'config', key: 'chartType' }, + options: [ + { label: 'Line', value: 'line' }, + { label: 'Bar', value: 'bar' }, + ], + }, + { + label: 'Data', + type: 'table-source', + target: { kind: 'config', key: 'rows' }, + help: 'First row is the header, first column the x-axis labels, the rest numeric series. Write it by hand or import a CSV/Excel file.', + }, + { + label: 'Base color', + type: 'text', + target: { kind: 'config', key: 'color' }, + placeholder: '#0084c4', + help: 'Color of the first series; others cycle through a palette.', + }, + { + label: 'Show legend', + type: 'boolean', + target: { kind: 'config', key: 'showLegend' }, + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.styles.ts b/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.styles.ts new file mode 100644 index 000000000..c888dfe34 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.styles.ts @@ -0,0 +1,26 @@ +import styled from 'styled-components' + +export const Container = styled.div` + width: 100%; +` + +export const Label = styled.label` + display: flex; + flex-direction: column; + gap: 4px; + font-size: 13px; + font-weight: 500; +` + +export const Input = styled.input` + padding: 6px 8px; + border: 1px solid #bbb; + border-radius: 4px; + font: inherit; +` + +export const HelperText = styled.div` + font-size: 12px; + color: #6f6f6f; + margin-top: 4px; +` diff --git a/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.tsx b/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.tsx new file mode 100644 index 000000000..01f68cc8a --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/datePicker.widget.tsx @@ -0,0 +1,81 @@ +import type { IUIPlugin } from '@development-framework/dm-core' +import { calendar } from '@equinor/eds-icons' +import { useState } from 'react' +import * as S from './datePicker.widget.styles' +import type { TWidgetDefinition } from './types' + +/** + * Date picker — a worked example of a self-contained widget. + * + * This whole file is the canonical reference for `builder/docs/ADDING_WIDGETS.md`: + * one file that co-locates the runtime component AND its registry definition. + * Copy it as a starting point for your own widgets. + */ + +interface DatePickerConfig { + label?: string + /** Starting date, as `YYYY-MM-DD`. */ + value?: string + helperText?: string +} + +/** The component rendered on the published page (and in the builder preview). */ +const DatePickerWidget = ( + props: Omit & { config: DatePickerConfig } +): React.ReactElement => { + const { label = 'Pick a date', value = '', helperText } = props.config + const [date, setDate] = useState(value) + + return ( + + + {label} + setDate(event.target.value)} + /> + + {helperText ? {helperText} : null} + + ) +} + +/** The registry definition — what makes it show up in the palette. */ +export const datePickerWidget: TWidgetDefinition = { + icon: calendar, + load: () => Promise.resolve({ default: DatePickerWidget }), + block: { + id: 'date-picker', + label: 'Date picker', + icon: 'calendar', + category: 'data', + description: 'Let visitors pick a date.', + contentModel: 'content', + defaultSize: { columns: 4, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-date-picker', + defaultConfig: { label: 'Pick a date' }, + hideTitle: true, + fields: [ + { + label: 'Label', + type: 'text', + target: { kind: 'config', key: 'label' }, + help: 'Caption shown above the picker.', + }, + { + label: 'Default date', + type: 'text', + target: { kind: 'config', key: 'value' }, + placeholder: '2026-07-01', + help: 'Optional starting date (YYYY-MM-DD).', + }, + { + label: 'Helper text', + type: 'text', + target: { kind: 'config', key: 'helperText' }, + help: 'Small hint shown beneath the picker.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/divider.widget.ts b/packages/dm-core-plugins/src/builder/widgets/divider.widget.ts new file mode 100644 index 000000000..60d1b31a2 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/divider.widget.ts @@ -0,0 +1,42 @@ +import { remove } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Divider — a self-contained horizontal rule. */ +export const dividerWidget: TWidgetDefinition = { + icon: remove, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticDividerPlugin, + })), + block: { + id: 'divider', + label: 'Divider', + icon: 'remove', + category: 'content', + description: 'A horizontal line to separate sections.', + contentModel: 'content', + defaultSize: { columns: 12, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-divider', + defaultConfig: { color: '#d8d8d8', thickness: 1, spacing: 8 }, + hideTitle: true, + fields: [ + { + label: 'Color', + type: 'text', + target: { kind: 'config', key: 'color' }, + placeholder: '#d8d8d8', + }, + { + label: 'Thickness (px)', + type: 'number', + target: { kind: 'config', key: 'thickness' }, + }, + { + label: 'Spacing (px)', + type: 'number', + target: { kind: 'config', key: 'spacing' }, + help: 'Empty space above and below the line.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/embed.widget.ts b/packages/dm-core-plugins/src/builder/widgets/embed.widget.ts new file mode 100644 index 000000000..045aa2678 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/embed.widget.ts @@ -0,0 +1,38 @@ +import { play_circle } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Video / Embed — a self-contained iframe for YouTube/Vimeo/any URL. */ +export const embedWidget: TWidgetDefinition = { + icon: play_circle, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticEmbedPlugin, + })), + block: { + id: 'embed', + label: 'Video / Embed', + icon: 'play_circle', + category: 'media', + description: 'Embed a YouTube/Vimeo video or any external page.', + contentModel: 'content', + defaultSize: { columns: 6, rows: 5 }, + recipe: '@development-framework/dm-core-plugins/static-embed', + defaultConfig: {}, + hideTitle: true, + fields: [ + { + label: 'URL', + type: 'text', + target: { kind: 'config', key: 'url' }, + placeholder: 'https://www.youtube.com/watch?v=…', + help: 'YouTube and Vimeo share links are converted automatically.', + }, + { + label: 'Title', + type: 'text', + target: { kind: 'config', key: 'title' }, + help: 'Accessible title for the embedded frame.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/heading.widget.ts b/packages/dm-core-plugins/src/builder/widgets/heading.widget.ts new file mode 100644 index 000000000..239faff63 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/heading.widget.ts @@ -0,0 +1,62 @@ +import { title } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Heading — a self-contained page/section title. */ +export const headingWidget: TWidgetDefinition = { + icon: title, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticHeadingPlugin, + })), + block: { + id: 'heading', + label: 'Heading', + icon: 'title', + category: 'content', + description: 'A page title or section heading.', + contentModel: 'content', + defaultSize: { columns: 8, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-heading', + defaultConfig: { text: 'Heading', level: 2, align: 'left' }, + hideTitle: true, + fields: [ + { + label: 'Text', + type: 'text', + target: { kind: 'config', key: 'text' }, + help: 'The heading text shown on the page.', + }, + { + label: 'Level', + type: 'select', + target: { kind: 'config', key: 'level' }, + options: [ + { label: 'H1 — Largest', value: 1 }, + { label: 'H2', value: 2 }, + { label: 'H3', value: 3 }, + { label: 'H4', value: 4 }, + { label: 'H5', value: 5 }, + { label: 'H6 — Smallest', value: 6 }, + ], + help: 'Heading level controls size and document outline.', + }, + { + label: 'Alignment', + type: 'select', + target: { kind: 'config', key: 'align' }, + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + { + label: 'Color', + type: 'text', + target: { kind: 'config', key: 'color' }, + placeholder: '#333 or red', + help: 'Text color (optional).', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/image.widget.ts b/packages/dm-core-plugins/src/builder/widgets/image.widget.ts new file mode 100644 index 000000000..0326b42e9 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/image.widget.ts @@ -0,0 +1,47 @@ +import { image } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** + * Image — media from a bound/uploaded file. Reuses the runtime `media-viewer` + * plugin, so it has no component of its own. + */ +export const imageWidget: TWidgetDefinition = { + icon: image, + block: { + id: 'image', + label: 'Image', + icon: 'image', + category: 'media', + description: 'An image or other media asset from a bound file.', + contentModel: 'content', + defaultSize: { columns: 4, rows: 4 }, + recipe: '@development-framework/dm-core-plugins/media-viewer', + defaultConfig: { fill: 'width' }, + fields: [ + { + label: 'Image', + type: 'image-upload', + target: { kind: 'config', key: 'address' }, + help: 'Browse your computer to upload an image, or bind a Scope to a file.', + }, + { + label: 'Caption', + type: 'text', + target: { kind: 'config', key: 'caption' }, + help: 'Text shown beneath the image.', + }, + { + label: 'Width (px)', + type: 'number', + target: { kind: 'config', key: 'width' }, + help: 'Fixed width in pixels (optional).', + }, + { + label: 'Height (px)', + type: 'number', + target: { kind: 'config', key: 'height' }, + help: 'Fixed height in pixels (optional).', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/index.ts b/packages/dm-core-plugins/src/builder/widgets/index.ts new file mode 100644 index 000000000..47fc55088 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/index.ts @@ -0,0 +1,79 @@ +import type { IconData } from '@equinor/eds-icons' +import { type ComponentType, lazy } from 'react' +import type { TBlock } from '../types' +import { buttonWidget } from './button.widget' +import { chartWidget } from './chart.widget' +import { datePickerWidget } from './datePicker.widget' +import { dividerWidget } from './divider.widget' +import { embedWidget } from './embed.widget' +import { headingWidget } from './heading.widget' +import { imageWidget } from './image.widget' +import { liveTableWidget } from './liveTable.widget' +import { metricWidget } from './metric.widget' +import { sectionWidget } from './section.widget' +import { spacerWidget } from './spacer.widget' +import { tableWidget } from './table.widget' +import { textWidget } from './text.widget' +import type { TWidgetDefinition } from './types' + +/** + * The widget registry — the single list of every widget in the palette. + * + * ★ To add a new widget: create a `*.widget.ts(x)` file next to this one that + * exports a `TWidgetDefinition`, import it above, and add it to this array. + * The palette card, its icon and (for static widgets) the runtime component + * are all wired up automatically. See `builder/docs/ADDING_WIDGETS.md`. + * + * The array order is the order widgets appear within their palette category. + */ +export const WIDGET_DEFINITIONS: TWidgetDefinition[] = [ + sectionWidget, + headingWidget, + textWidget, + buttonWidget, + dividerWidget, + spacerWidget, + imageWidget, + embedWidget, + tableWidget, + chartWidget, + metricWidget, + liveTableWidget, + datePickerWidget, +] + +/** The palette catalogue, derived from the registry. */ +export const BLOCKS: TBlock[] = WIDGET_DEFINITIONS.map( + (definition) => definition.block +) + +export const getBlock = (id: string): TBlock | undefined => + BLOCKS.find((block) => block.id === id) + +/** Icon data for every widget, keyed by `block.icon` for palette lookups. */ +export const WIDGET_ICONS: Record = Object.fromEntries( + WIDGET_DEFINITIONS.map((definition) => [ + definition.block.icon, + definition.icon, + ]) +) + +/** + * Runtime plugin-map entries for builder-owned "static" widgets, keyed by + * recipe. Spread into the package's plugin map in `src/index.tsx`. Widgets that + * reuse an existing plugin (no `load`) are skipped — they need no new component. + */ +export const builderStaticPlugins: Record< + string, + { component: ComponentType } +> = Object.fromEntries( + WIDGET_DEFINITIONS.filter((definition) => definition.load).map( + (definition) => [ + definition.block.recipe, + // biome-ignore lint/style/noNonNullAssertion: filtered to widgets with a loader + { component: lazy(definition.load!) }, + ] + ) +) + +export type { TWidgetDefinition } diff --git a/packages/dm-core-plugins/src/builder/widgets/liveTable.widget.ts b/packages/dm-core-plugins/src/builder/widgets/liveTable.widget.ts new file mode 100644 index 000000000..d956c11ae --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/liveTable.widget.ts @@ -0,0 +1,50 @@ +import { table_chart } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** + * Live table — a scope-bound, viewer-editable table. + * + * Unlike the static Table widget (which stores its rows inline in the site + * config), this widget reads from and writes to a DMSS entity. Bind it to an + * entity that has a `rows: string[][]` attribute via the Data scope field in + * the inspector. Viewers can then add rows, delete rows and edit cells and + * click Save to persist their changes back to DMSS. + */ +export const liveTableWidget: TWidgetDefinition = { + icon: table_chart, + load: () => + import('../static/LiveTablePlugin').then((m) => ({ + default: m.LiveTablePlugin, + })), + block: { + id: 'live-table', + label: 'Live table', + icon: 'table_chart', + category: 'data', + description: + 'An editable table that reads from and writes to a DMSS entity. Bind to an entity via the Data scope.', + contentModel: 'data', + defaultSize: { columns: 6, rows: 4 }, + recipe: '@development-framework/dm-core-plugins/static-live-table', + defaultConfig: { allowAddRows: true, allowDeleteRows: true }, + hideTitle: false, + fields: [ + { + label: 'Caption', + type: 'text', + target: { kind: 'config', key: 'caption' }, + help: 'Optional heading shown above the table.', + }, + { + label: 'Allow viewers to add rows', + type: 'boolean', + target: { kind: 'config', key: 'allowAddRows' }, + }, + { + label: 'Allow viewers to delete rows', + type: 'boolean', + target: { kind: 'config', key: 'allowDeleteRows' }, + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/metric.widget.ts b/packages/dm-core-plugins/src/builder/widgets/metric.widget.ts new file mode 100644 index 000000000..c3f3fee11 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/metric.widget.ts @@ -0,0 +1,88 @@ +import { functions } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Metric — a self-contained big-number KPI computed from a list of values. */ +export const metricWidget: TWidgetDefinition = { + icon: functions, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticMetricPlugin, + })), + block: { + id: 'metric', + label: 'Metric', + icon: 'functions', + category: 'data', + description: 'A single big-number KPI computed from a list of values.', + contentModel: 'content', + defaultSize: { columns: 3, rows: 2 }, + recipe: '@development-framework/dm-core-plugins/static-metric', + defaultConfig: { + label: 'Average', + values: '12, 18, 9, 24, 15', + aggregation: 'mean', + decimals: 2, + }, + hideTitle: true, + fields: [ + { + label: 'Label', + type: 'text', + target: { kind: 'config', key: 'label' }, + help: 'Caption shown above the number.', + }, + { + label: 'Values', + type: 'textarea', + target: { kind: 'config', key: 'values' }, + placeholder: '12, 18, 9, 24, 15', + help: 'Numbers separated by commas, spaces or new lines. Non-numbers are ignored.', + }, + { + label: 'Calculation', + type: 'select', + target: { kind: 'config', key: 'aggregation' }, + options: [ + { label: 'Average (mean)', value: 'mean' }, + { label: 'Sum', value: 'sum' }, + { label: 'Minimum', value: 'min' }, + { label: 'Maximum', value: 'max' }, + { label: 'Median', value: 'median' }, + { label: 'Std. deviation', value: 'stddev' }, + { label: 'Count', value: 'count' }, + ], + help: 'How the values are reduced to a single number.', + }, + { + label: 'Unit', + type: 'text', + target: { kind: 'config', key: 'unit' }, + placeholder: '%, kg, ms…', + help: 'Optional unit shown after the number.', + }, + { + label: 'Decimals', + type: 'number', + target: { kind: 'config', key: 'decimals' }, + help: 'Number of decimal places to display.', + }, + { + label: 'Color', + type: 'text', + target: { kind: 'config', key: 'color' }, + placeholder: '#243746', + help: 'Color of the big number (optional).', + }, + { + label: 'Alignment', + type: 'select', + target: { kind: 'config', key: 'align' }, + options: [ + { label: 'Left', value: 'left' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'right' }, + ], + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/section.widget.ts b/packages/dm-core-plugins/src/builder/widgets/section.widget.ts new file mode 100644 index 000000000..533f70117 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/section.widget.ts @@ -0,0 +1,26 @@ +import { view_module } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** + * Section — a container widget. Reuses the runtime `grid` plugin, so it has no + * component of its own: dropping it creates a nested grid you can drill into. + */ +export const sectionWidget: TWidgetDefinition = { + icon: view_module, + block: { + id: 'section', + label: 'Section', + icon: 'view_module', + category: 'layout', + description: 'A container to group and arrange other widgets.', + contentModel: 'content', + defaultSize: { columns: 8, rows: 4 }, + recipe: '@development-framework/dm-core-plugins/grid', + defaultConfig: { + size: { columns: 12, rows: 8, rowGap: '8px', columnGap: '8px' }, + items: [], + itemBorder: { size: '1px', style: 'solid', color: '#bbb', radius: '5px' }, + showItemBorders: false, + }, + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/spacer.widget.ts b/packages/dm-core-plugins/src/builder/widgets/spacer.widget.ts new file mode 100644 index 000000000..d3120dd1e --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/spacer.widget.ts @@ -0,0 +1,31 @@ +import { keyboard_space_bar } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Spacer — self-contained empty vertical whitespace. */ +export const spacerWidget: TWidgetDefinition = { + icon: keyboard_space_bar, + load: () => + import('../static/staticWidgets').then((m) => ({ + default: m.StaticSpacerPlugin, + })), + block: { + id: 'spacer', + label: 'Spacer', + icon: 'keyboard_space_bar', + category: 'content', + description: 'Empty vertical space between widgets.', + contentModel: 'content', + defaultSize: { columns: 12, rows: 1 }, + recipe: '@development-framework/dm-core-plugins/static-spacer', + defaultConfig: { height: 24 }, + hideTitle: true, + fields: [ + { + label: 'Height (px)', + type: 'number', + target: { kind: 'config', key: 'height' }, + help: 'How tall the empty space is.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/table.widget.ts b/packages/dm-core-plugins/src/builder/widgets/table.widget.ts new file mode 100644 index 000000000..8e2931073 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/table.widget.ts @@ -0,0 +1,43 @@ +import { table_chart } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** Table — a self-contained table authored inline or imported from a file. */ +export const tableWidget: TWidgetDefinition = { + icon: table_chart, + load: () => + import('../static/StaticTablePlugin').then((m) => ({ + default: m.StaticTablePlugin, + })), + block: { + id: 'table', + label: 'Table', + icon: 'table_chart', + category: 'data', + description: 'A table you fill in by hand or from a CSV/Excel file.', + contentModel: 'content', + defaultSize: { columns: 8, rows: 4 }, + recipe: '@development-framework/dm-core-plugins/static-table', + defaultConfig: { + rows: [ + ['Column A', 'Column B'], + ['Row 1', 'Value'], + ['Row 2', 'Value'], + ], + pageSize: 25, + }, + fields: [ + { + label: 'Data', + type: 'table-source', + target: { kind: 'config', key: 'rows' }, + help: 'Write the table as markdown, or import a CSV/Excel file. Large files are paginated.', + }, + { + label: 'Rows per page', + type: 'number', + target: { kind: 'config', key: 'pageSize' }, + help: 'How many rows to show per page.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/text.widget.ts b/packages/dm-core-plugins/src/builder/widgets/text.widget.ts new file mode 100644 index 000000000..bb4d72515 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/text.widget.ts @@ -0,0 +1,29 @@ +import { text_field } from '@equinor/eds-icons' +import type { TWidgetDefinition } from './types' + +/** + * Text — markdown content. Reuses the runtime `markdown` plugin, so it has no + * component of its own. + */ +export const textWidget: TWidgetDefinition = { + icon: text_field, + block: { + id: 'text', + label: 'Text', + icon: 'text_field', + category: 'content', + description: 'Markdown rendered from a bound document.', + contentModel: 'content', + defaultSize: { columns: 6, rows: 2 }, + recipe: '@development-framework/dm-core-plugins/markdown', + defaultConfig: { content: 'Write your text here.' }, + fields: [ + { + label: 'Content', + type: 'textarea', + target: { kind: 'config', key: 'content' }, + help: 'Markdown text shown in this widget. Bind a Scope instead to use a document.', + }, + ], + }, +} diff --git a/packages/dm-core-plugins/src/builder/widgets/types.ts b/packages/dm-core-plugins/src/builder/widgets/types.ts new file mode 100644 index 000000000..5390b19c0 --- /dev/null +++ b/packages/dm-core-plugins/src/builder/widgets/types.ts @@ -0,0 +1,32 @@ +import type { IconData } from '@equinor/eds-icons' +import type { ComponentType } from 'react' +import type { TBlock } from '../types' + +/** + * A widget definition is the single source of truth for one palette widget. + * + * Everything the builder needs to know about a widget lives in one file: + * - `block` — how it looks and behaves in the palette + inspector. + * - `icon` — the EDS icon rendered on its palette card. + * - `load` — (static widgets only) a lazy loader for the React component that + * renders the widget at runtime, registered under `block.recipe`. + * + * To add a new widget you create one `*.widget.ts(x)` file that exports one of + * these, then add a single line to `widgets/index.ts`. Nothing else to touch. + * See `builder/docs/ADDING_WIDGETS.md` for a step-by-step guide. + */ +export type TWidgetDefinition = { + /** Palette + inspector metadata. Also feeds `getBlock` / `BLOCKS`. */ + block: TBlock + /** EDS icon shown on the palette card; keyed by `block.icon`. */ + icon: IconData + /** + * Lazy loader for a builder-owned "static" widget component. When present, + * the component is registered in the runtime plugin map under `block.recipe`. + * + * Omit this for widgets that reuse an already-registered plugin (Section → + * `grid`, Text → `markdown`, Image → `media-viewer`, Form → `form`, Job → + * `job`). Those only need a palette block, not a new component. + */ + load?: () => Promise<{ default: ComponentType }> +} diff --git a/packages/dm-core-plugins/src/grid/GridElement.tsx b/packages/dm-core-plugins/src/grid/GridElement.tsx index 0dde5a461..182e995ed 100644 --- a/packages/dm-core-plugins/src/grid/GridElement.tsx +++ b/packages/dm-core-plugins/src/grid/GridElement.tsx @@ -24,6 +24,39 @@ const Element = styled.div` overflow: auto; ` +const HORIZONTAL_ALIGN: Record = { + left: 'flex-start', + center: 'center', + right: 'flex-end', +} + +const VERTICAL_ALIGN: Record = { + top: 'flex-start', + center: 'center', + bottom: 'flex-end', +} + +const styleToCss = (style?: TGridItem['style']): React.CSSProperties => + style + ? { + textAlign: style.textAlign, + // The content wrapper is a flex column, so horizontal alignment of + // block widgets (image, button, embed…) is driven by `align-items`, + // derived from the same control as text alignment. Vertical placement + // within the cell is driven by `justify-content`. + alignItems: style.textAlign + ? HORIZONTAL_ALIGN[style.textAlign] + : undefined, + justifyContent: style.verticalAlign + ? VERTICAL_ALIGN[style.verticalAlign] + : undefined, + fontSize: style.fontSize, + fontWeight: style.bold ? 700 : undefined, + color: style.color, + padding: style.padding, + } + : {} + type TGridItemProps = { idReference: string item: TGridItem @@ -45,8 +78,12 @@ export const GridElement = (props: TGridItemProps): React.ReactElement => { $showItemBorders={showItemBorders} $itemBorder={itemBorder} > - {item?.title && {item.title}} - + {item?.title && ( + + {item.title} + + )} + + import('./builder/BuilderPlugin').then((module) => ({ + default: module.BuilderPlugin, + })) + ), + }, + '@development-framework/dm-core-plugins/builder/directory': { + component: lazy(() => + import('./builder/SiteDirectoryPlugin').then((module) => ({ + default: module.SiteDirectoryPlugin, + })) + ), + }, '@development-framework/dm-core-plugins/explorer': { component: lazy(() => import('./explorer/ExplorerPlugin')), }, @@ -21,6 +72,10 @@ export default { })) ), }, + // Builder "static" widgets (Heading, Button, Table, Chart, Metric, …) are + // registered from the widget registry so adding one never touches this file. + // See builder/widgets/index.ts and builder/docs/ADDING_WIDGETS.md. + ...builderStaticPlugins, '@development-framework/dm-core-plugins/stack': { component: lazy(() => import('./stack/StackPlugin').then((module) => ({ diff --git a/packages/dm-core-plugins/src/markdown/MarkdownPlugin.tsx b/packages/dm-core-plugins/src/markdown/MarkdownPlugin.tsx index 0ba4e7384..f90e68636 100644 --- a/packages/dm-core-plugins/src/markdown/MarkdownPlugin.tsx +++ b/packages/dm-core-plugins/src/markdown/MarkdownPlugin.tsx @@ -7,7 +7,7 @@ import { } from '@development-framework/dm-core' import axios from 'axios' import hljs from 'highlight.js' -import { useMemo, useState } from 'react' +import { useEffect, useState } from 'react' import Markdown from 'react-markdown' import remarkGfm from 'remark-gfm' import type { MediaObject } from '../mediaViewer/MediaViewerPlugin.types' @@ -16,78 +16,104 @@ import 'highlight.js/styles/github-dark.css' import { Icon } from '@equinor/eds-core-react' import { checkbox, checkbox_outline } from '@equinor/eds-icons' -export const MarkdownPlugin = (props: IUIPlugin) => { - const { idReference, config } = props +const markdownComponents = { + input(props: any) { + if (props.type === 'checkbox') { + return ( + + ) + } + return + }, + code({ node, inline, className, children, ...props }: any) { + const definedLanguage = /language-(\w+)/.exec(className || '') + return !inline && definedLanguage ? ( + + ) : ( + + {children} + + ) + }, +} + +const MarkdownView = ({ markdownString }: { markdownString?: string }) => ( + + + {markdownString} + + +) + +const InlineMarkdown = ({ content }: { content: string }) => ( + +) + +const FileMarkdown = ({ idReference }: { idReference: string }) => { const { dmssAPI } = useApplication() const [markdownString, setMarkdownString] = useState() + const [fetchError, setFetchError] = useState() const { document, isLoading, error } = useDocument( idReference, - 1 + 1, + false ) - useMemo(async () => { - if (document) { - const { dataSource } = splitAddress(idReference) - const response = await dmssAPI.blobGetById( - { - dataSourceId: dataSource, - blobId: document?.content?.address.slice(1), - }, - { responseType: 'blob' } - ) - const blobFile = new Blob([response.data], { - type: 'text/markdown', - }) - const syntheticBlobUrl = window.URL.createObjectURL(blobFile) - const file = await axios.get(syntheticBlobUrl) - setMarkdownString(file.data) + useEffect(() => { + const fetchMarkdown = async () => { + if (document) { + const { dataSource } = splitAddress(idReference) + const response = await dmssAPI.blobGetById( + { + dataSourceId: dataSource, + blobId: document?.content?.address.slice(1), + }, + { responseType: 'blob' } + ) + const blobFile = new Blob([response.data], { + type: 'text/markdown', + }) + const syntheticBlobUrl = window.URL.createObjectURL(blobFile) + try { + const file = await axios.get(syntheticBlobUrl) + setMarkdownString(file.data) + } finally { + window.URL.revokeObjectURL(syntheticBlobUrl) + } + } } - }, [document]) + void fetchMarkdown().catch((err) => + setFetchError( + err instanceof Error + ? err + : new Error(err?.toString() ?? 'Unknown error') + ) + ) + }, [document, idReference, dmssAPI]) if (error) throw new Error(JSON.stringify(error, null, 2)) + if (fetchError) throw fetchError if (isLoading || document === null) return + return +} - return ( - - - ) - } - return - }, - code({ node, inline, className, children, ...props }: any) { - const definedLanguage = /language-(\w+)/.exec(className || '') - return !inline && definedLanguage ? ( - - ) : ( - - {children} - - ) - }, - }} - > - {markdownString} - - - ) +export const MarkdownPlugin = (props: IUIPlugin) => { + const inlineContent: string | undefined = (props.config as any)?.content + if (inlineContent !== undefined) + return + return } diff --git a/packages/dm-core-plugins/src/mediaViewer/MediaViewerPlugin.tsx b/packages/dm-core-plugins/src/mediaViewer/MediaViewerPlugin.tsx index 8ae6268ae..e0a119f56 100644 --- a/packages/dm-core-plugins/src/mediaViewer/MediaViewerPlugin.tsx +++ b/packages/dm-core-plugins/src/mediaViewer/MediaViewerPlugin.tsx @@ -22,13 +22,13 @@ export const MediaViewerPlugin = ( props: Omit & { config: MediaViewerPluginConfig } ) => { const { idReference, config } = props + // A directly uploaded image stores its absolute File address in the config so + // the widget no longer depends on a page attribute (scope) binding. + const targetRef = config.address || idReference const [blobUrl, setBlobUrl] = useState() const { dmssAPI } = useApplication() - const { document, isLoading, error } = useDocument( - idReference, - 1 - ) - const { dataSource } = useMemo(() => splitAddress(idReference), [idReference]) + const { document, isLoading, error } = useDocument(targetRef, 1) + const { dataSource } = useMemo(() => splitAddress(targetRef), [targetRef]) const [contentType, canPreview] = useMemo(() => { const normalizedFileType = document?.filetype?.toLowerCase() // normalize if your keys are lowercase const contentType = @@ -78,7 +78,17 @@ export const MediaViewerPlugin = ( if (error) throw new Error(JSON.stringify(error, null, 2)) if (isLoading || document === null) return - if (document.type !== EBlueprint.FILE) throw new Error('This is not a file') + if (document.type !== EBlueprint.FILE) + return ( + + No image bound. Upload an image from your computer in the inspector, or + set this widget's Scope to a file attribute. + + ) return ( =16.8.0" + checksum: 10c0/be0bf41716dc58f9386bc36906ec1ce72b7b42b6d1d0e631d347afe9bd8714a829bd6f58a346dd089b1519e93918ae2f94497411a61a4f5e4d9247c6cfd1fef8 + languageName: node + linkType: hard + +"@dnd-kit/core@npm:^6.3.1": + version: 6.3.1 + resolution: "@dnd-kit/core@npm:6.3.1" + dependencies: + "@dnd-kit/accessibility": "npm:^3.1.1" + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/196db95d81096d9dc248983533eab91ba83591770fa5c894b1ac776f42af0d99522b3fd5bb3923411470e4733fcfa103e6ee17adc17b9b7eb54c7fbec5ff7c52 + languageName: node + linkType: hard + +"@dnd-kit/modifiers@npm:^9.0.0": + version: 9.0.0 + resolution: "@dnd-kit/modifiers@npm:9.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: 10c0/ca8cc9da8296df10774d779c1611074dc327ccc3c49041c102111c98c7f2b2b73b6af5209c0eef6b2fe978ac63dc2a985efa87c85a8d786577304bd2e64cee1d + languageName: node + linkType: hard + +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" + dependencies: + "@dnd-kit/utilities": "npm:^3.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: 10c0/37ee48bc6789fb512dc0e4c374a96d19abe5b2b76dc34856a5883aaa96c3297891b94cc77bbc409e074dcce70967ebcb9feb40cd9abadb8716fc280b4c7f99af + languageName: node + linkType: hard + +"@dnd-kit/utilities@npm:^3.2.2": + version: 3.2.2 + resolution: "@dnd-kit/utilities@npm:3.2.2" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/9aa90526f3e3fd567b5acc1b625a63177b9e8d00e7e50b2bd0e08fa2bf4dba7e19529777e001fdb8f89a7ce69f30b190c8364d390212634e0afdfa8c395e85a0 + languageName: node + linkType: hard + +"@emnapi/core@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/core@npm:1.10.0" + dependencies: + "@emnapi/wasi-threads": "npm:1.2.1" + tslib: "npm:^2.4.0" + checksum: 10c0/f51d08227857b60632de7714d708124f0e100a1462dde6df8221760939aa3204a73193830371830fac0716f3ccd2129f2cac1b17cd7d7958bc4da9018a296edb + languageName: node + linkType: hard + +"@emnapi/runtime@npm:1.10.0": + version: 1.10.0 + resolution: "@emnapi/runtime@npm:1.10.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/953f14991d1aefb92ee6f8eb27dea725e484791a53a0cb5f47d9e0087b9a2c929ff2e92adf95af15d6ad456db6300c6b761ebf72b50a875b874a83520b3ba093 + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.2.1": + version: 1.2.1 + resolution: "@emnapi/wasi-threads@npm:1.2.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/32fcfa81ab396533b2ec1f4082b1ff779a05d9c836bbbd3f4398405b0e6814c0d9503b7993130e37bc6941dbc1ded49f55e9700ae9ca4e803bab2b5bc5deb331 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:1.4.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@equinor/eds-core-react@npm:^2.5.0": + version: 2.5.0 + resolution: "@equinor/eds-core-react@npm:2.5.0" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@equinor/eds-icons": "npm:^1.4.0" + "@equinor/eds-tokens": "npm:^2.2.0" + "@equinor/eds-utils": "npm:^2.1.0" + "@floating-ui/react": "npm:^0.27.19" + "@internationalized/date": "npm:^3.12.0" + "@react-aria/utils": "npm:^3.33.1" + "@react-stately/calendar": "npm:^3.9.3" + "@react-stately/datepicker": "npm:^3.16.1" + "@react-types/shared": "npm:^3.33.1" + "@tanstack/react-virtual": "npm:3.13.23" + downshift: "npm:9.3.2" + react-aria: "npm:^3.47.0" + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + styled-components: ^6 + checksum: 10c0/bab939b7dab423a9f90e4625f0ffc140e702786f2c2ba80a554ac97496d061fd5e2d5d8e4ce37b45f45bdce832a6c903ccf5d55a8e321ee32fe089b429acbccf + languageName: node + linkType: hard + +"@equinor/eds-icons@npm:^1.4.0": + version: 1.4.0 + resolution: "@equinor/eds-icons@npm:1.4.0" + checksum: 10c0/1756089fdfc752139946ec852c81dd1ddc43c2d502ba2ef8b2b36199f31bb7d23224287c4a006190b2f25457ac0d047cbfc16544a9b68aba910f41f267b4afcf + languageName: node + linkType: hard + +"@equinor/eds-tokens@npm:^2.2.0": + version: 2.2.0 + resolution: "@equinor/eds-tokens@npm:2.2.0" + checksum: 10c0/e5e698a9156046e3f6e14bdb10d08d3657e57872f5d256dda96c0ffce3285efee5f6948779ade85e4487f9a599c0b5e3b7ece7455e589b2162e51b115f58a482 + languageName: node + linkType: hard + +"@equinor/eds-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "@equinor/eds-utils@npm:2.1.0" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@equinor/eds-tokens": "npm:^2.2.0" + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + styled-components: ^6 + checksum: 10c0/c69e7f641ba8a76abf7ff42cc55008f01cdcb23a4b1485e818291e6679a468dc385dbe85070a8c6f5a3b854cea693f0eec313d3b7a588da21990aed7d06169e8 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@floating-ui/core@npm:^1.7.5": + version: 1.7.5 + resolution: "@floating-ui/core@npm:1.7.5" + dependencies: + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/f9c52205e198b231d63a387b09c659aab08c46a1899e0b0bbe147b8b4f048b546f15ba17cb5d2a471da9534f1883d979425e13e5c4ceee67be63e4b0abd4db5d + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.7.6": + version: 1.7.6 + resolution: "@floating-ui/dom@npm:1.7.6" + dependencies: + "@floating-ui/core": "npm:^1.7.5" + "@floating-ui/utils": "npm:^0.2.11" + checksum: 10c0/5c098e0d7b58c9bc769f276cca1766994c2c9c70c92d091a61bba8b3e9be53c011e0a79a8457fc2fb2f3d91697a26eb52e0a4962ef936dc963b45f58613c212f + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.8": + version: 2.1.8 + resolution: "@floating-ui/react-dom@npm:2.1.8" + dependencies: + "@floating-ui/dom": "npm:^1.7.6" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/26260ca4bb23b57c73b824062505abf977a008ce6e0463bdacca74f7e49853c4cd1d2bbf1a77c6caa17fa37dfffda2c6c4cd07a8737ebd7474aaff7818401d75 + languageName: node + linkType: hard + +"@floating-ui/react@npm:^0.27.19": + version: 0.27.19 + resolution: "@floating-ui/react@npm:0.27.19" + dependencies: + "@floating-ui/react-dom": "npm:^2.1.8" + "@floating-ui/utils": "npm:^0.2.11" + tabbable: "npm:^6.0.0" + peerDependencies: + react: ">=17.0.0" + react-dom: ">=17.0.0" + checksum: 10c0/2a2cdfd3e67e0606833b63f922ad2a9037974f22b944e1cb8c0991b4c40450f8413d69745c0bbf4646e5ba283747f60d2fdc9a8d289b68b24448e59d81a3a96d + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.11": + version: 0.2.11 + resolution: "@floating-ui/utils@npm:0.2.11" + checksum: 10c0/f4bcea1559bdbb721ecc8e8ead423ac58d6a5b6e70b602cf0810ba6ad4ed1c77211b207faa88b278a9042f0c743133de08a203ed6741c1b6443423332884d5b3 + languageName: node + linkType: hard + +"@iconify/types@npm:^2.0.0": + version: 2.0.0 + resolution: "@iconify/types@npm:2.0.0" + checksum: 10c0/65a3be43500c7ccacf360e136d00e1717f050b7b91da644e94370256ac66f582d59212bdb30d00788aab4fc078262e91c95b805d1808d654b72f6d2072a7e4b2 + languageName: node + linkType: hard + +"@iconify/utils@npm:^3.0.2": + version: 3.1.0 + resolution: "@iconify/utils@npm:3.1.0" + dependencies: + "@antfu/install-pkg": "npm:^1.1.0" + "@iconify/types": "npm:^2.0.0" + mlly: "npm:^1.8.0" + checksum: 10c0/a39445e892b248486c186306e1ccba4b07ed1d5b21b143ddf279b33062063173feb84954b9a82e05713b927872787d6c0081073d23f55c44294de37615d4a1f7 + languageName: node + linkType: hard + +"@internationalized/date@npm:^3.12.0": + version: 3.12.0 + resolution: "@internationalized/date@npm:3.12.0" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/6a26495d32f010b227a1f506da02cdf8438506014b41cfb81576c707a3dfe3d0fd207f80bcf28acd9eef8248a2c2da115cf9016515d513653ea1b22a796d0246 + languageName: node + linkType: hard + +"@internationalized/date@npm:^3.12.1": + version: 3.12.1 + resolution: "@internationalized/date@npm:3.12.1" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/e06efa10bab7fcd88cdac1268d21f2e0fcdd6a4b5c2c9a6fdf381c94a166f3188ebb7a92b3405f3b689577bc18f98447433bedddc8885aab6a169bf0943b3875 + languageName: node + linkType: hard + +"@internationalized/number@npm:^3.6.5": + version: 3.6.5 + resolution: "@internationalized/number@npm:3.6.5" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/f87d710863a8dbf057aac311193c82f3c42e862abdd99e5b71034f1022926036552620eab5dd00c23e975f28b9e41e830cb342ba0264436749d9cdc5ae031d44 + languageName: node + linkType: hard + +"@internationalized/number@npm:^3.6.6": + version: 3.6.6 + resolution: "@internationalized/number@npm:3.6.6" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/70fe93dcbeb9b2048240dd26e98186ead2c7a14fc508da0ba5d995a3deb4a8176956361774ca927b4106bbd3f8411315b80de7cf9bfc727c0cff802b460e75d2 + languageName: node + linkType: hard + +"@internationalized/string@npm:^3.2.7": + version: 3.2.7 + resolution: "@internationalized/string@npm:3.2.7" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/8f7bea379ce047026ef20d535aa1bd7612a5e5a5108d1e514965696a46bce34e38111411943b688d00dae2c81eae7779ae18343961310696d32ebb463a19b94a + languageName: node + linkType: hard + +"@internationalized/string@npm:^3.2.8": + version: 3.2.8 + resolution: "@internationalized/string@npm:3.2.8" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/3bcda163578a447b88c8972f3cbb18bb60de595099930ca82f1848eed85e5b303d6d840505791564e425d9717607b36ee166367ec79b359521386f16143459c9 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: "npm:^5.3.1" + find-up: "npm:^4.1.0" + get-package-type: "npm:^0.1.0" + js-yaml: "npm:^3.13.1" + resolve-from: "npm:^5.0.0" + checksum: 10c0/dd2a8b094887da5a1a2339543a4933d06db2e63cbbc2e288eb6431bd832065df0c099d091b6a67436e71b7d6bf85f01ce7c15f9253b4cbebcc3b9a496165ba42 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2, @istanbuljs/schema@npm:^0.1.3": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/7be408781d0a6f657e969cbec13b540c329671819c2f57acfad0dae9dbfe2c9be859f38fe99b35dba9ff1536937dc6ddc69fdcd2794812fa3c647a1619797f6c + languageName: node + linkType: hard + +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/934f7bf73190f029ac0f96662c85cd276ec460d407baf6b0dbaec2872e157db4d55a7ee0b1c43b18874602f662b37cb973dda469a4e6d88b4e4845b521adeeb2 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10c0/c7b1b40c618f8baf4d00609022d2afa086d9c6acc706f303a70bb4b67275868f620ad2e1a9efc5edd418906157337cce50589a627a6400bbdf117d351b91ef86 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10c0/60b79d23a5358dc50d9510d726443316253ecda3a7fb8072e1526b3e0d3b14f066ee112db95699b7a43ad3f0b61b750c72e28a5a1cac361d7a2bb34747fa938a + languageName: node + linkType: hard + +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b41f193fb697d3ced134349250aed6ccea075e48c4f803159db102b826a4e473397c68c31118259868fd69a5cba70e97e1c26d2c2ff716ca39dc73a2ccec037e + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/cf0a8bcda801b28dc2e2b2ba36302200ee8104a45ad7a21e6c234148932f826cb3bc57c8df3b7b815aeea0861d7b6ca6f0d4778f93b9219398ef28749e03595c + languageName: node + linkType: hard + +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10c0/a385c99396878fe6e4460c43bd7bb0a5cc52befb462cc6e7f2a3810f9e7bcce7cdeb51908fd530391ee452dc856c98baa2c5f5fa8a5b30b071d31ef7f6955cea + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10c0/a754402a799541c6e5aff2c8160562525e2a47e7d568f01ebfc4da66522de39cbb809bbb0a841c7052e4270d79214e70aec3c169e4eae42a03bc1a8a20cb9fa2 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10c0/b329e89cd5f20b9278ae1233df74016ebf7b385e0d14b9f4c1ad18d096c4c19d1e687aa113a9c976b16ec07f021ae53dea811fb8c1248a50ac34fbe009fdf6be + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10c0/a2f177081830a2e8ad3f2e29e20b63bd40bade294880b595acf2fc09ec74b6a9dd98f126a2baa2bf4941acd89b13a4ade5351b3885c224107083a0059b60a219 + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10c0/7de54090e54a674ca173470b55dc1afdee994f2d70d185c80236003efd3fa2b753fff51ffcdda8e2890244c411fd2267529d42c4a50a8303755041ee493e6a04 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10c0/593a8c4272797bb5628984486080cbf57aed09c7cfdc0a634e8c06c38c6bef329c46c0016e84555ee55d1cd1f381518cf1890990ff845524c1123720c8c1481b + languageName: node + linkType: hard + +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10c0/7f4a7f73dcf45dfdf280c7aa283cbac7b6e5a904813c3a93ead7e55873761fc20d5c4f0191d2019004fac6f55f061c82eb3249c2901164ad80e362e7a7ede5a6 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + "@types/istanbul-reports": "npm:^3.0.0" + "@types/node": "npm:*" + "@types/yargs": "npm:^17.0.8" + chalk: "npm:^4.0.0" + checksum: 10c0/ea4e493dd3fb47933b8ccab201ae573dcc451f951dc44ed2a86123cd8541b82aa9d2b1031caf9b1080d6673c517e2dcc25a44b2dc4f3fbc37bfc965d444888c0 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 10c0/2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10c0/fa425b606d7c7ee5bfa6a31a7b050dd5814b4082f318e0e4190f991902181b4330f43f4805db1dd4f2433fd0ed9cc7a7b9c2683f1deeab1df1b0a98b1e24055b + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@mapbox/geojson-rewind@npm:^0.5.2": + version: 0.5.2 + resolution: "@mapbox/geojson-rewind@npm:0.5.2" + dependencies: + get-stream: "npm:^6.0.1" + minimist: "npm:^1.2.6" + bin: + geojson-rewind: geojson-rewind + checksum: 10c0/631f89ba5b656cb1e02197c242b231f98da0afb96815fa26481497176d6bd5f2aac77af4950da91c954094694acbc26382bd3d38146705737e8ff06442d95a12 + languageName: node + linkType: hard + +"@mapbox/geojson-types@npm:^1.0.2": + version: 1.0.2 + resolution: "@mapbox/geojson-types@npm:1.0.2" + checksum: 10c0/aa0a2cb95a358d8756ab5aa70356bcbd6f554a4571703a88a09e7db6580061d6ef4054db5fe3ecb2817c383b8b5433746a8f46712dc606b32063f73b154f99fc + languageName: node + linkType: hard + +"@mapbox/jsonlint-lines-primitives@npm:^2.0.2, @mapbox/jsonlint-lines-primitives@npm:~2.0.2": + version: 2.0.2 + resolution: "@mapbox/jsonlint-lines-primitives@npm:2.0.2" + checksum: 10c0/5814e42fc453700132f93ea742aabcef9a3c98d9bf17d4c1106f82d1dcd91bbc93052e66e29014323b9b2a41b020c743d897e4a96cc4ed2f734482d587d8c2b2 + languageName: node + linkType: hard + +"@mapbox/mapbox-gl-supported@npm:^1.5.0": + version: 1.5.0 + resolution: "@mapbox/mapbox-gl-supported@npm:1.5.0" + peerDependencies: + mapbox-gl: ">=0.32.1 <2.0.0" + checksum: 10c0/5b7712e8b546e598dc5152632504cad53081211b64ddc4447825840ddca703275bc36599167b9550ab906ca8a9554936bcdae562073fdef24b8d38d78ee262fb + languageName: node + linkType: hard + +"@mapbox/point-geometry@npm:0.1.0, @mapbox/point-geometry@npm:^0.1.0, @mapbox/point-geometry@npm:~0.1.0": + version: 0.1.0 + resolution: "@mapbox/point-geometry@npm:0.1.0" + checksum: 10c0/e4d861908574cb3165f5ad37b000416ebc90a2d6b3e0073191e6b6dc5074a6159d84ac5114d78557399bb429134f0d05bfb529e7902d1cb2b36d722b72ab662c + languageName: node + linkType: hard + +"@mapbox/tiny-sdf@npm:^1.1.1": + version: 1.2.5 + resolution: "@mapbox/tiny-sdf@npm:1.2.5" + checksum: 10c0/de0252388a628ddb491c986c715f0b63ca6a74f5dac16d3e51eb75a21935a31e34fba5db47c81cc59a462d782021fc68b2e3cc119b2d6cabe15d31e311674d6c + languageName: node + linkType: hard + +"@mapbox/tiny-sdf@npm:^2.0.6": + version: 2.0.6 + resolution: "@mapbox/tiny-sdf@npm:2.0.6" + checksum: 10c0/cb272578a30c88d6694937af9b084106aa251e92c71089e7d57b0df8152fd0ce0598d5816182a4cd478dc40b188ea680cb6d53f4385107719424beabe7ed4e13 + languageName: node + linkType: hard + +"@mapbox/unitbezier@npm:^0.0.0": + version: 0.0.0 + resolution: "@mapbox/unitbezier@npm:0.0.0" + checksum: 10c0/af1943ebeb7532317a5cedfc38d0e580b7bd76cc5c43988df65541d377f3e3fa7d68c201dda20f5239213a4bc81ec5d13146354107196ffc4f14d6f39b8343b5 + languageName: node + linkType: hard + +"@mapbox/unitbezier@npm:^0.0.1": + version: 0.0.1 + resolution: "@mapbox/unitbezier@npm:0.0.1" + checksum: 10c0/97f39d4fbdf9579d0a1a8be0d536eb113a805d36459e774014f488a7ca6cc9dcfc77ab7a2ebe5af395ad50da6efb4dbf2566de0db3f62b6b8675cddbace8f86a + languageName: node + linkType: hard + +"@mapbox/vector-tile@npm:^1.3.1": + version: 1.3.1 + resolution: "@mapbox/vector-tile@npm:1.3.1" + dependencies: + "@mapbox/point-geometry": "npm:~0.1.0" + checksum: 10c0/ffb271b95c383923768295e72bdf95e428efb906434b864ea04d3853a8373cf0de19f039bd6615f7cf018fbfb4dbf4599f27ebaa86c2b7b09f7d69187f8d7da1 + languageName: node + linkType: hard + +"@mapbox/whoots-js@npm:^3.1.0": + version: 3.1.0 + resolution: "@mapbox/whoots-js@npm:3.1.0" + checksum: 10c0/fe9e959a9049bcbc2c05d9d1156e050191ad697a1bd95e41cdfa069051ff1d6f2930ced234a8d68d5a0bf78091feab30d76497418ec800d90f0aac8691fe4fd4 + languageName: node + linkType: hard + +"@maplibre/maplibre-gl-style-spec@npm:^20.3.1": + version: 20.4.0 + resolution: "@maplibre/maplibre-gl-style-spec@npm:20.4.0" + dependencies: + "@mapbox/jsonlint-lines-primitives": "npm:~2.0.2" + "@mapbox/unitbezier": "npm:^0.0.1" + json-stringify-pretty-compact: "npm:^4.0.0" + minimist: "npm:^1.2.8" + quickselect: "npm:^2.0.0" + rw: "npm:^1.3.3" + tinyqueue: "npm:^3.0.0" + bin: + gl-style-format: dist/gl-style-format.mjs + gl-style-migrate: dist/gl-style-migrate.mjs + gl-style-validate: dist/gl-style-validate.mjs + checksum: 10c0/3a0bfc72af3a787005a0bc7364eb7f3e9575e5d6b86c78403130873a6333a9a37199c5aca406ac9290e166c751d2b2471c8c8ba78ce31ebd0278e3fc721bad4b + languageName: node + linkType: hard + +"@mermaid-js/parser@npm:^1.1.0": + version: 1.1.0 + resolution: "@mermaid-js/parser@npm:1.1.0" + dependencies: + langium: "npm:^4.0.0" + checksum: 10c0/449e594a1f9ff1e24da7eb66b7d85694f1c48bdf1d27e798e0e182659e907fd3438a69bf30754807a8a8e286ab3173fa1b8e43ee102be31e30ee7632b9bc1d03 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.4": + version: 1.1.4 + resolution: "@napi-rs/wasm-runtime@npm:1.1.4" + dependencies: + "@tybys/wasm-util": "npm:^0.10.1" + peerDependencies: + "@emnapi/core": ^1.7.1 + "@emnapi/runtime": ^1.7.1 + checksum: 10c0/2e88e1955258949ccf2d18c79975821ad38071b465ef126a5e14110977b97868867b016c1ad046e963cccc42c0bd9db6c8ff5fd1ebb61b87bb3487f339041658 + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.127.0": + version: 0.127.0 + resolution: "@oxc-project/types@npm:0.127.0" + checksum: 10c0/52c0947ac64a9ca119fe971f947e784a35ecd14a072fa3f542a58a5f6c42010b53f2bf92731e39b9899b83c990a9517bbd29d1e5a5b7b489e52616685c6a9278 + languageName: node + linkType: hard + +"@plotly/d3-sankey-circular@npm:0.33.1": + version: 0.33.1 + resolution: "@plotly/d3-sankey-circular@npm:0.33.1" + dependencies: + d3-array: "npm:^1.2.1" + d3-collection: "npm:^1.0.4" + d3-shape: "npm:^1.2.0" + elementary-circuits-directed-graph: "npm:^1.0.4" + checksum: 10c0/7a1c7caa4297099a0403ad1e2c4a5446f8239fce4b9a4ac44c5bc144d56e4c0eb0761dea05993db8b17a85934f9f1723ded2e008e6096d0c226b481313e6fe29 + languageName: node + linkType: hard + +"@plotly/d3-sankey@npm:0.7.2": + version: 0.7.2 + resolution: "@plotly/d3-sankey@npm:0.7.2" + dependencies: + d3-array: "npm:1" + d3-collection: "npm:1" + d3-shape: "npm:^1.2.0" + checksum: 10c0/d3443749f9e7ea692ac1f19c740bac40e25df293af738164d4e313bcc6fd27d35822d5f83530cc9c1c1472d0be49e409edafbbf1ca58164aeca0ec4fe2a392e5 + languageName: node + linkType: hard + +"@plotly/d3@npm:3.8.2": + version: 3.8.2 + resolution: "@plotly/d3@npm:3.8.2" + checksum: 10c0/e578fd3d3850b2c2671e0fcc856676925e81da79f25c940cf2160dc19fe846924181c7c7ea0f03d499d3d4d1ff552d36506a828f5a6482db5707fb2bc4a29ebc + languageName: node + linkType: hard + +"@plotly/mapbox-gl@npm:1.13.4": + version: 1.13.4 + resolution: "@plotly/mapbox-gl@npm:1.13.4" + dependencies: + "@mapbox/geojson-rewind": "npm:^0.5.2" + "@mapbox/geojson-types": "npm:^1.0.2" + "@mapbox/jsonlint-lines-primitives": "npm:^2.0.2" + "@mapbox/mapbox-gl-supported": "npm:^1.5.0" + "@mapbox/point-geometry": "npm:^0.1.0" + "@mapbox/tiny-sdf": "npm:^1.1.1" + "@mapbox/unitbezier": "npm:^0.0.0" + "@mapbox/vector-tile": "npm:^1.3.1" + "@mapbox/whoots-js": "npm:^3.1.0" + csscolorparser: "npm:~1.0.3" + earcut: "npm:^2.2.2" + geojson-vt: "npm:^3.2.1" + gl-matrix: "npm:^3.2.1" + grid-index: "npm:^1.1.0" + murmurhash-js: "npm:^1.0.0" + pbf: "npm:^3.2.1" + potpack: "npm:^1.0.1" + quickselect: "npm:^2.0.0" + rw: "npm:^1.3.3" + supercluster: "npm:^7.1.0" + tinyqueue: "npm:^2.0.3" + vt-pbf: "npm:^3.1.1" + checksum: 10c0/e528983335ef8d8ef564e2c0b21a98888b2220750d069679a334d91a43487d12dbbaaf6c798e47ba42f8a5d17dd996207cb1f2ed081d00705631d8cbfcba0a44 + languageName: node + linkType: hard + +"@plotly/point-cluster@npm:^3.1.9": + version: 3.1.9 + resolution: "@plotly/point-cluster@npm:3.1.9" + dependencies: + array-bounds: "npm:^1.0.1" + binary-search-bounds: "npm:^2.0.4" + clamp: "npm:^1.0.1" + defined: "npm:^1.0.0" + dtype: "npm:^2.0.0" + flatten-vertex-data: "npm:^1.0.2" + is-obj: "npm:^1.0.1" + math-log2: "npm:^1.0.1" + parse-rect: "npm:^1.2.0" + pick-by-alias: "npm:^1.2.0" + checksum: 10c0/3e451b1b04f1ce9c2ff1dcc2adbbc5d56bebc419f49c903eb215fa6973315d7f279fe9ebffea6f7ff0c09ba1c21bfe393bb74f54d0e5522a2d20bf43b7f35c38 + languageName: node + linkType: hard + +"@plotly/regl@npm:^2.1.2": + version: 2.1.2 + resolution: "@plotly/regl@npm:2.1.2" + checksum: 10c0/84043676e5372113200deaca890829e457b95a0d6171342479d211264480b5fa2db81579f6082a1236755b0bd362dc0e9bfa4db7bc45cc6c50888e18a5ebbae6 + languageName: node + linkType: hard + +"@react-aria/ssr@npm:^3.9.10": + version: 3.9.10 + resolution: "@react-aria/ssr@npm:3.9.10" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/44acb4c441d9c5d65aab94aa81fd8368413cf2958ab458582296dd78f6ba4783583f2311fa986120060e5c26b54b1f01e8910ffd17e4f41ccc5fc8c357d84089 + languageName: node + linkType: hard + +"@react-aria/utils@npm:^3.33.1": + version: 3.33.1 + resolution: "@react-aria/utils@npm:3.33.1" + dependencies: + "@react-aria/ssr": "npm:^3.9.10" + "@react-stately/flags": "npm:^3.1.2" + "@react-stately/utils": "npm:^3.11.0" + "@react-types/shared": "npm:^3.33.1" + "@swc/helpers": "npm:^0.5.0" + clsx: "npm:^2.0.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/8b59b6e4f5f2358aecc291c929b621e68d121ebd15a8377da0a8b34ee706c9caea928dc34a0cc70650527dc9538619f4a9dcbcacf92bee31a73e69f59afbc772 + languageName: node + linkType: hard + +"@react-stately/calendar@npm:^3.9.3": + version: 3.9.3 + resolution: "@react-stately/calendar@npm:3.9.3" + dependencies: + "@internationalized/date": "npm:^3.12.0" + "@react-stately/utils": "npm:^3.11.0" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/shared": "npm:^3.33.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/24231a0b0c270b9936a043b4e17091005fce0749804f1b0ea7e0f101f945081255d08439186d0229430f2ade745c9d2eaa131e0b154d236e0dc3b0912a93864e + languageName: node + linkType: hard + +"@react-stately/datepicker@npm:^3.16.1": + version: 3.16.1 + resolution: "@react-stately/datepicker@npm:3.16.1" + dependencies: + "@internationalized/date": "npm:^3.12.0" + "@internationalized/number": "npm:^3.6.5" + "@internationalized/string": "npm:^3.2.7" + "@react-stately/form": "npm:^3.2.4" + "@react-stately/overlays": "npm:^3.6.23" + "@react-stately/utils": "npm:^3.11.0" + "@react-types/datepicker": "npm:^3.13.5" + "@react-types/shared": "npm:^3.33.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/7b4d044e9f5feb30ee5d4ac66fce9294a97464471d659d6fc35d6d0d4f8280283b1cfcbf5f232ac38ed16f4b765b15ac6d67399efb3f78488cf4e46d015e4c56 + languageName: node + linkType: hard + +"@react-stately/flags@npm:^3.1.2": + version: 3.1.2 + resolution: "@react-stately/flags@npm:3.1.2" + dependencies: + "@swc/helpers": "npm:^0.5.0" + checksum: 10c0/d86890ce662f04c7d8984e9560527f46c9779b97757abded9e1bf7e230a6900a0ea7a3e7c22534de8d2ff278abae194e4e4ad962d710f3b04c52a4e1011c2e5b + languageName: node + linkType: hard + +"@react-stately/form@npm:^3.2.4": + version: 3.2.4 + resolution: "@react-stately/form@npm:3.2.4" + dependencies: + "@react-types/shared": "npm:^3.33.1" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/e940fd5a8fa0fcd3f09acdc3eb97b12fa84690d6a4fc01a52da22673e4a2580b76626901296cad7ee7ed9ea43deb9a48e6af248bf4d59a4fe4c599f30941ecb3 + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.6.23": + version: 3.6.23 + resolution: "@react-stately/overlays@npm:3.6.23" + dependencies: + "@react-stately/utils": "npm:^3.11.0" + "@react-types/overlays": "npm:^3.9.4" + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/7c62160e11bbeb58780a629e09f0c8b16716a06b5c6114909fc45bb0118d55913b50aab3bbee2d6f60bbdd1075fed234dd361b3595b6cc49115a370c66b01ff3 + languageName: node + linkType: hard + +"@react-stately/utils@npm:^3.11.0": + version: 3.11.0 + resolution: "@react-stately/utils@npm:3.11.0" + dependencies: + "@swc/helpers": "npm:^0.5.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/09b38438df19fd8ff14d3147b2f9e5d42869b3ee637b0e33d6f2ab5cba93612e640c6de339b766b8c825d7bef828851fd551d5a197a037eb1331913546b8516c + languageName: node + linkType: hard + +"@react-types/calendar@npm:^3.8.3": + version: 3.8.3 + resolution: "@react-types/calendar@npm:3.8.3" + dependencies: + "@internationalized/date": "npm:^3.12.0" + "@react-types/shared": "npm:^3.33.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/c1f129b2f431227d7150639f95201c50f32299813e3e8714f2457335023675ff61b08508b67a687ceef18c0449e1b07c7863688e2710615be746cde6ffb60c0b + languageName: node + linkType: hard + +"@react-types/datepicker@npm:^3.13.5": + version: 3.13.5 + resolution: "@react-types/datepicker@npm:3.13.5" + dependencies: + "@internationalized/date": "npm:^3.12.0" + "@react-types/calendar": "npm:^3.8.3" + "@react-types/overlays": "npm:^3.9.4" + "@react-types/shared": "npm:^3.33.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/e011c1d0844ddd36ed50448fb83aec602fc3c6db4f32a5c6d76d3bb168debafb170aa880102de4abb664ff2f77d140037b63ca4a375f4b1574989ce5b471c3c3 + languageName: node + linkType: hard + +"@react-types/overlays@npm:^3.9.4": + version: 3.9.4 + resolution: "@react-types/overlays@npm:3.9.4" + dependencies: + "@react-types/shared": "npm:^3.33.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/ecf332ad553029db14cf4a001893ade13f56bb6fc003357d2854166067c2b5dd2229e976cca79bf707c3f7c47d5dedb675d9b2ce45000114e4531dc5aaf00e04 + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.33.1": + version: 3.33.1 + resolution: "@react-types/shared@npm:3.33.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/072dbf92de80e3535441fbf4a9075009636221974d0b70bd1078ec1e343ae1373d69d2c02c2fba0963e50f4b134872144ffaf1573daec8477233e408f96e008c + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.34.0": + version: 3.34.0 + resolution: "@react-types/shared@npm:3.34.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/0bc3c8748aeba03257d3bbfe6ef0f7b24a10c25f1193f58403cdf2f0e988eef0cd3318c17dd09e26451a934dbbf2158c21dc7be2ca4f9c0012814d3ef8c78cda + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-android-arm64@npm:1.0.0-rc.17" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-rc.17" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-rc.17" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-rc.17" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-rc.17" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-rc.17" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-rc.17" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.0.0-rc.17" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.0.0-rc.17" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-rc.17" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-rc.17" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.0.0-rc.17" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-rc.17" + dependencies: + "@emnapi/core": "npm:1.10.0" + "@emnapi/runtime": "npm:1.10.0" + "@napi-rs/wasm-runtime": "npm:^1.1.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-rc.17" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-rc.17" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.17" + checksum: 10c0/5e840b20cc531910c093c1ca36e550952cf4936465a50d89f0a98fc9d0dfd7d319d06a10a5f4376209d89e9bf4d60af6cc8363ebf0dcc5e60842f7fef438b2f0 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-rc.7": + version: 1.0.0-rc.7 + resolution: "@rolldown/pluginutils@npm:1.0.0-rc.7" + checksum: 10c0/9d5490b5805b25bcd1720ca01c4c032b55a0ef953dab36a8dd42c568e82214576baa464f3027cd5dff3fabcfbe3bf3db2251d12b60220f5d1cd2ffde5ee37082 + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^5.2.0": + version: 5.3.0 + resolution: "@rollup/pluginutils@npm:5.3.0" + dependencies: + "@types/estree": "npm:^1.0.0" + estree-walker: "npm:^2.0.2" + picomatch: "npm:^4.0.2" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/001834bf62d7cf5bac424d2617c113f7f7d3b2bf3c1778cbcccb72cdc957b68989f8e7747c782c2b911f1dde8257f56f8ac1e779e29e74e638e3f1e2cac2bcd0 + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.59.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-android-arm64@npm:4.59.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.59.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.59.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.59.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.59.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.59.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.59.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.59.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.59.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.59.0" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.59.0" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.59.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.59.0" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.59.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.59.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.59.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.59.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.59.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-openbsd-x64@npm:4.59.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.59.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.59.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.59.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.59.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.59.0": + version: 4.59.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.59.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10c0/ef6351ae073c45c2ac89494dbb3e1f87cc60a93ce4cde797b782812b6f97da0d620ae81973f104b43c9b7eaa789ad20ba4f6a1359f1cc62f63729a55a7d22d4e + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.1 + resolution: "@sinonjs/commons@npm:3.0.1" + dependencies: + type-detect: "npm:4.0.8" + checksum: 10c0/1227a7b5bd6c6f9584274db996d7f8cee2c8c350534b9d0141fc662eaf1f292ea0ae3ed19e5e5271c8fd390d27e492ca2803acd31a1978be2cdc6be0da711403 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10c0/2e2fb6cc57f227912814085b7b01fede050cd4746ea8d49a1e44d5a0e56a804663b0340ae2f11af7559ea9bf4d087a11f2f646197a660ea3cb04e19efc04aa63 + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/a50bd0baa34faf16bcba712091f94c7f0e230431fe99a9dfc3401fa92823ad3f68495b86ab9bf9044b53839e8c416cfbb37eb3f246ff33f261e0fa9ee1779c5b + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/8a98e59bd9971e066815b4129409932f7a4db4866834fe75677ea6d517972fb40b380a69a4413189f20e7947411f9ab1b0f029dd5e8068686a5a0188d3ccd4c7 + languageName: node + linkType: hard + +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/517dcca75223bd05d3f056a8514dbba3031278bea4eadf0842c576d84f4651e7a4e0e7082d3ee4ef42456de0f9c4531d8a1917c04876ca64b014b859ca8f1bde + languageName: node + linkType: hard + +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/004bd1892053b7e9c1b0bb14acc44e77634ec393722b87b1e4fae53e2c35122a2dd0d5c15e9070dbeec274e22e7693a2b8b48506733a8009ee92b12946fcb10a + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/80e0a7fcf902f984c705051ca5c82ea6050ccbb70b651a8fea6d0eb5809e4dac274b49ea6be2d87f1eb9dfc0e2d6cdfffe1669ec2117f44b67a60a07d4c0b8b8 + languageName: node + linkType: hard + +"@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/73e92c8277a89279745c0c500f59f083279a8dc30cd552b22981fade2a77628fb2bd2819ee505725fcd2e93f923e3790b52efcff409a159e657b46604a0b9a21 + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/655ed6bc7a208ceaa4ecff0a54ccc36008c3cb31efa90d11e171cab325ebbb21aa78f09c7b65f9b3ddeda3a85f348c0c862902c48be13c14b4de165c847974e3 + languageName: node + linkType: hard + +"@svgr/babel-plugin-transform-svg-component@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/4ac00bb99a3db4ef05e4362f116a3c608ee365a2d26cf7318d8d41a4a5b30a02c80455cce0e62c65b60ed815b5d632bedabac2ccd4b56f998fadef5286e3ded4 + languageName: node + linkType: hard + +"@svgr/babel-preset@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-preset@npm:8.1.0" + dependencies: + "@svgr/babel-plugin-add-jsx-attribute": "npm:8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute": "npm:8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression": "npm:8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value": "npm:8.0.0" + "@svgr/babel-plugin-svg-dynamic-title": "npm:8.0.0" + "@svgr/babel-plugin-svg-em-dimensions": "npm:8.0.0" + "@svgr/babel-plugin-transform-react-native-svg": "npm:8.1.0" + "@svgr/babel-plugin-transform-svg-component": "npm:8.0.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/49367d3ad0831f79b1056871b91766246f449d4d1168623af5e283fbaefce4a01d77ab00de6b045b55e956f9aae27895823198493cd232d88d3435ea4517ffc5 + languageName: node + linkType: hard + +"@svgr/core@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/core@npm:8.1.0" + dependencies: + "@babel/core": "npm:^7.21.3" + "@svgr/babel-preset": "npm:8.1.0" + camelcase: "npm:^6.2.0" + cosmiconfig: "npm:^8.1.3" + snake-case: "npm:^3.0.4" + checksum: 10c0/6a2f6b1bc79bce39f66f088d468985d518005fc5147ebf4f108570a933818b5951c2cb7da230ddff4b7c8028b5a672b2d33aa2acce012b8b9770073aa5a2d041 + languageName: node + linkType: hard + +"@svgr/hast-util-to-babel-ast@npm:8.0.0": + version: 8.0.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:8.0.0" + dependencies: + "@babel/types": "npm:^7.21.3" + entities: "npm:^4.4.0" + checksum: 10c0/f4165b583ba9eaf6719e598977a7b3ed182f177983e55f9eb55a6a73982d81277510e9eb7ab41f255151fb9ed4edd11ac4bef95dd872f04ed64966d8c85e0f79 + languageName: node + linkType: hard + +"@svgr/plugin-jsx@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-jsx@npm:8.1.0" + dependencies: + "@babel/core": "npm:^7.21.3" + "@svgr/babel-preset": "npm:8.1.0" + "@svgr/hast-util-to-babel-ast": "npm:8.0.0" + svg-parser: "npm:^2.0.4" + peerDependencies: + "@svgr/core": "*" + checksum: 10c0/07b4d9e00de795540bf70556fa2cc258774d01e97a12a26234c6fdf42b309beb7c10f31ee24d1a71137239347b1547b8bb5587d3a6de10669f95dcfe99cddc56 + languageName: node + linkType: hard + +"@swc/core-darwin-arm64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-arm64@npm:1.15.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-darwin-x64@npm:1.15.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.18" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.18" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.18" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.18" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-linux-x64-musl@npm:1.15.18" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.18" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.15.18": + version: 1.15.18 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.15.11": + version: 1.15.18 + resolution: "@swc/core@npm:1.15.18" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.18" + "@swc/core-darwin-x64": "npm:1.15.18" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.18" + "@swc/core-linux-arm64-gnu": "npm:1.15.18" + "@swc/core-linux-arm64-musl": "npm:1.15.18" + "@swc/core-linux-x64-gnu": "npm:1.15.18" + "@swc/core-linux-x64-musl": "npm:1.15.18" + "@swc/core-win32-arm64-msvc": "npm:1.15.18" + "@swc/core-win32-ia32-msvc": "npm:1.15.18" + "@swc/core-win32-x64-msvc": "npm:1.15.18" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.25" + peerDependencies: + "@swc/helpers": ">=0.5.17" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10c0/b52381eefaf88ba1b8ff603c58418fcc7ca661cd0fc8b68bfc17e523b356d027dfcc3bfd459ddc107a67021a943979a79cd0589338d20c6564bbb5cf8f35afb5 + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.3": + version: 0.1.3 + resolution: "@swc/counter@npm:0.1.3" + checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 + languageName: node + linkType: hard + +"@swc/helpers@npm:^0.5.0": + version: 0.5.15 + resolution: "@swc/helpers@npm:0.5.15" + dependencies: + tslib: "npm:^2.8.0" + checksum: 10c0/33002f74f6f885f04c132960835fdfc474186983ea567606db62e86acd0680ca82f34647e8e610f4e1e422d1c16fce729dde22cd3b797ab1fd9061a825dabca4 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10c0/847a5b20b131281f89d640a7ed4887fb65724807d53d334b230e84b98c21097aa10cd28a074f9ed287a6ce109e443dd4bafbe7dcfb62333d7806c4ea3e7f8aca + languageName: node + linkType: hard + +"@tanstack/query-core@npm:5.100.1": + version: 5.100.1 + resolution: "@tanstack/query-core@npm:5.100.1" + checksum: 10c0/62eebf0b493d5931fd0f7946bc321736d9467b9c2e1e52f02eac930a221c38e465305b29920ef6437d18fe10c7f0f3671d2ff60e218672134ef104a2f33edaf5 + languageName: node + linkType: hard + +"@tanstack/react-query@npm:^5.100.1": + version: 5.100.1 + resolution: "@tanstack/react-query@npm:5.100.1" + dependencies: + "@tanstack/query-core": "npm:5.100.1" + peerDependencies: + react: ^18 || ^19 + checksum: 10c0/23617f2197b400c32ea15bf448d7cc03b0441f8c431e9c4ecf48c5a85cbfdbca48b4d07ec0590984bd9795e1c9fd7e828f57a2bbe808c2526edde09e15815250 + languageName: node + linkType: hard + +"@tanstack/react-virtual@npm:3.13.23": + version: 3.13.23 + resolution: "@tanstack/react-virtual@npm:3.13.23" + dependencies: + "@tanstack/virtual-core": "npm:3.13.23" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/a3e33463eb471067f9539858655bbefb99778f930156165afb0218fe5ffa181bc2e3dacfe3b6f655f1716d9994064f58ef009370975f801b4c83826aa33a5791 + languageName: node + linkType: hard + +"@tanstack/virtual-core@npm:3.13.23": + version: 3.13.23 + resolution: "@tanstack/virtual-core@npm:3.13.23" + checksum: 10c0/3487089ea4e1dcbb01836f51a1b35b709f9ba03312fe600ddb97c893c16be213e1690af735717b1d6b2e22dfec691c338bc96b5965228c195840249699d5273a + languageName: node + linkType: hard + +"@testing-library/dom@npm:^10.4.1": + version: 10.4.1 + resolution: "@testing-library/dom@npm:10.4.1" + dependencies: + "@babel/code-frame": "npm:^7.10.4" + "@babel/runtime": "npm:^7.12.5" + "@types/aria-query": "npm:^5.0.1" + aria-query: "npm:5.3.0" + dom-accessibility-api: "npm:^0.5.9" + lz-string: "npm:^1.5.0" + picocolors: "npm:1.1.1" + pretty-format: "npm:^27.0.2" + checksum: 10c0/19ce048012d395ad0468b0dbcc4d0911f6f9e39464d7a8464a587b29707eed5482000dad728f5acc4ed314d2f4d54f34982999a114d2404f36d048278db815b1 + languageName: node + linkType: hard + +"@testing-library/react@npm:^16.3.2": + version: 16.3.2 + resolution: "@testing-library/react@npm:16.3.2" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/f9c7f0915e1b5f7b750e6c7d8b51f091b8ae7ea99bacb761d7b8505ba25de9cfcb749a0f779f1650fb268b499dd79165dc7e1ee0b8b4cb63430d3ddc81ffe044 + languageName: node + linkType: hard + +"@testing-library/user-event@npm:14.6.1": + version: 14.6.1 + resolution: "@testing-library/user-event@npm:14.6.1" + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: 10c0/75fea130a52bf320d35d46ed54f3eec77e71a56911b8b69a3fe29497b0b9947b2dc80d30f04054ad4ce7f577856ae3e5397ea7dff0ef14944d3909784c7a93fe + languageName: node + linkType: hard + +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: 10c0/073bfa548026b1ebaf1659eb8961e526be22fa77139b10d60e712f46d2f0f05f4e6c8bec62a087d41088ee9e29faa7f54838568e475ab2f776171003c3920858 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node10@npm:1.0.11" + checksum: 10c0/28a0710e5d039e0de484bdf85fee883bfd3f6a8980601f4d44066b0a6bcd821d31c4e231d1117731c4e24268bd4cf2a788a6787c12fc7f8d11014c07d582783c + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10c0/dddca2b553e2bee1308a056705103fc8304e42bb2d2cbd797b84403a223b25c78f2c683ec3e24a095e82cd435387c877239bffcb15a590ba817cd3f6b9a99fd9 + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10c0/67c1316d065fdaa32525bc9449ff82c197c4c19092b9663b23213c8cbbf8d88b6ed6a17898e0cbc2711950fbfaf40388938c1c748a2ee89f7234fc9e7fe2bf44 + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10c0/05f8f2734e266fb1839eb1d57290df1664fe2aa3b0fdd685a9035806daa635f7519bf6d5d9b33f6e69dd545b8c46bd6e2b5c79acb2b1f146e885f7f11a42a5bb + languageName: node + linkType: hard + +"@turf/area@npm:^7.1.0": + version: 7.2.0 + resolution: "@turf/area@npm:7.2.0" + dependencies: + "@turf/helpers": "npm:^7.2.0" + "@turf/meta": "npm:^7.2.0" + "@types/geojson": "npm:^7946.0.10" + tslib: "npm:^2.8.1" + checksum: 10c0/4a7dc2ff92e84bf9b46de349eb227056c53506530021358d1a03ca622e6ba6685f592ce59a9a770ec564d99a7c4156179e9e733e5ebce4e35f5151fd959b1c57 + languageName: node + linkType: hard + +"@turf/bbox@npm:^7.1.0": + version: 7.2.0 + resolution: "@turf/bbox@npm:7.2.0" + dependencies: + "@turf/helpers": "npm:^7.2.0" + "@turf/meta": "npm:^7.2.0" + "@types/geojson": "npm:^7946.0.10" + tslib: "npm:^2.8.1" + checksum: 10c0/766d59d5f75c272481e971cd4004e139962607e8f34391b2abfb15bb34f9544a0479ceb14772565e005e4a12fdd82adf0d440ab1c9e0decbde6de50a5706db43 + languageName: node + linkType: hard + +"@turf/centroid@npm:^7.1.0": + version: 7.2.0 + resolution: "@turf/centroid@npm:7.2.0" + dependencies: + "@turf/helpers": "npm:^7.2.0" + "@turf/meta": "npm:^7.2.0" + "@types/geojson": "npm:^7946.0.10" + tslib: "npm:^2.8.1" + checksum: 10c0/afdf72cbd140337638528055c509cc76c7839d3b0d6d0379d9bb1d9d04db4af840f1c17855dff6202df76072e87b2d9f02d1cd571f63a5ac704bd3a62c235afd + languageName: node + linkType: hard + +"@turf/helpers@npm:^7.2.0": + version: 7.2.0 + resolution: "@turf/helpers@npm:7.2.0" + dependencies: + "@types/geojson": "npm:^7946.0.10" + tslib: "npm:^2.8.1" + checksum: 10c0/4d6f57164cca00ec7a18e2d3c0200d0274e4ab2b6b3201c6a867b867d899f3f618a82ae242617d467efb04f32740cec150ae06a0e07ee39318397ebc34914687 + languageName: node + linkType: hard + +"@turf/meta@npm:^7.2.0": + version: 7.2.0 + resolution: "@turf/meta@npm:7.2.0" + dependencies: + "@turf/helpers": "npm:^7.2.0" + "@types/geojson": "npm:^7946.0.10" + checksum: 10c0/707ed63ba64fe48769806bf2419f5c0cd2ebf821a6467aeffb784ba7ebd6a63ec98d4192b97915948529c00304ed46ddc83842a80714fb1f2018fd4e3c455498 + languageName: node + linkType: hard + +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/b255094f293794c6d2289300c5fbcafbb5532a3aed3a5ffd2f8dc1828e639b88d75f6a376dd8f94347a44813fd7a7149d8463477a9a49525c8b2dcaa38c2d1e8 + languageName: node + linkType: hard + +"@types/aria-query@npm:^5.0.1": + version: 5.0.4 + resolution: "@types/aria-query@npm:5.0.4" + checksum: 10c0/dc667bc6a3acc7bba2bccf8c23d56cb1f2f4defaa704cfef595437107efaa972d3b3db9ec1d66bc2711bfc35086821edd32c302bffab36f2e79b97f312069f08 + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.8 + resolution: "@types/babel__generator@npm:7.6.8" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/f0ba105e7d2296bf367d6e055bb22996886c114261e2cb70bf9359556d0076c7a57239d019dee42bb063f565bade5ccb46009bce2044b2952d964bf9a454d6d2 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.20.6 + resolution: "@types/babel__traverse@npm:7.20.6" + dependencies: + "@babel/types": "npm:^7.20.7" + checksum: 10c0/7ba7db61a53e28cac955aa99af280d2600f15a8c056619c05b6fc911cbe02c61aa4f2823299221b23ce0cce00b294c0e5f618ec772aa3f247523c2e48cf7b888 + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + +"@types/d3-array@npm:*": + version: 3.2.1 + resolution: "@types/d3-array@npm:3.2.1" + checksum: 10c0/38bf2c778451f4b79ec81a2288cb4312fe3d6449ecdf562970cc339b60f280f31c93a024c7ff512607795e79d3beb0cbda123bb07010167bce32927f71364bca + languageName: node + linkType: hard + +"@types/d3-axis@npm:*": + version: 3.0.6 + resolution: "@types/d3-axis@npm:3.0.6" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/d756d42360261f44d8eefd0950c5bb0a4f67a46dd92069da3f723ac36a1e8cb2b9ce6347d836ef19d5b8aef725dbcf8fdbbd6cfbff676ca4b0642df2f78b599a + languageName: node + linkType: hard + +"@types/d3-brush@npm:*": + version: 3.0.6 + resolution: "@types/d3-brush@npm:3.0.6" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/fd6e2ac7657a354f269f6b9c58451ffae9d01b89ccb1eb6367fd36d635d2f1990967215ab498e0c0679ff269429c57fad6a2958b68f4d45bc9f81d81672edc01 + languageName: node + linkType: hard + +"@types/d3-chord@npm:*": + version: 3.0.6 + resolution: "@types/d3-chord@npm:3.0.6" + checksum: 10c0/c5a25eb5389db01e63faec0c5c2ec7cc41c494e9b3201630b494c4e862a60f1aa83fabbc33a829e7e1403941e3c30d206c741559b14406ac2a4239cfdf4b4c17 + languageName: node + linkType: hard + +"@types/d3-color@npm:*": + version: 3.1.3 + resolution: "@types/d3-color@npm:3.1.3" + checksum: 10c0/65eb0487de606eb5ad81735a9a5b3142d30bc5ea801ed9b14b77cb14c9b909f718c059f13af341264ee189acf171508053342142bdf99338667cea26a2d8d6ae + languageName: node + linkType: hard + +"@types/d3-contour@npm:*": + version: 3.0.6 + resolution: "@types/d3-contour@npm:3.0.6" + dependencies: + "@types/d3-array": "npm:*" + "@types/geojson": "npm:*" + checksum: 10c0/e7d83e94719af4576ceb5ac7f277c5806f83ba6c3631744ae391cffc3641f09dfa279470b83053cd0b2acd6784e8749c71141d05bdffa63ca58ffb5b31a0f27c + languageName: node + linkType: hard + +"@types/d3-delaunay@npm:*": + version: 6.0.4 + resolution: "@types/d3-delaunay@npm:6.0.4" + checksum: 10c0/d154a8864f08c4ea23ecb9bdabcef1c406a25baa8895f0cb08a0ed2799de0d360e597552532ce7086ff0cdffa8f3563f9109d18f0191459d32bb620a36939123 + languageName: node + linkType: hard + +"@types/d3-dispatch@npm:*": + version: 3.0.6 + resolution: "@types/d3-dispatch@npm:3.0.6" + checksum: 10c0/405eb7d0ec139fbf72fa6a43b0f3ca8a1f913bb2cb38f607827e63fca8d4393f021f32f3b96b33c93ddbd37789453a0b3624f14f504add5308fd9aec8a46dda0 + languageName: node + linkType: hard + +"@types/d3-drag@npm:*": + version: 3.0.7 + resolution: "@types/d3-drag@npm:3.0.7" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/65e29fa32a87c72d26c44b5e2df3bf15af21cd128386bcc05bcacca255927c0397d0cd7e6062aed5f0abd623490544a9d061c195f5ed9f018fe0b698d99c079d + languageName: node + linkType: hard + +"@types/d3-dsv@npm:*": + version: 3.0.7 + resolution: "@types/d3-dsv@npm:3.0.7" + checksum: 10c0/c0f01da862465594c8a28278b51c850af3b4239cc22b14fd1a19d7a98f93d94efa477bf59d8071beb285dca45bf614630811451e18e7c52add3a0abfee0a1871 + languageName: node + linkType: hard + +"@types/d3-ease@npm:*": + version: 3.0.2 + resolution: "@types/d3-ease@npm:3.0.2" + checksum: 10c0/aff5a1e572a937ee9bff6465225d7ba27d5e0c976bd9eacdac2e6f10700a7cb0c9ea2597aff6b43a6ed850a3210030870238894a77ec73e309b4a9d0333f099c + languageName: node + linkType: hard + +"@types/d3-fetch@npm:*": + version: 3.0.7 + resolution: "@types/d3-fetch@npm:3.0.7" + dependencies: + "@types/d3-dsv": "npm:*" + checksum: 10c0/3d147efa52a26da1a5d40d4d73e6cebaaa964463c378068062999b93ea3731b27cc429104c21ecbba98c6090e58ef13429db6399238c5e3500162fb3015697a0 + languageName: node + linkType: hard + +"@types/d3-force@npm:*": + version: 3.0.10 + resolution: "@types/d3-force@npm:3.0.10" + checksum: 10c0/c82b459079a106b50e346c9b79b141f599f2fc4f598985a5211e72c7a2e20d35bd5dc6e91f306b323c8bfa325c02c629b1645f5243f1c6a55bd51bc85cccfa92 + languageName: node + linkType: hard + +"@types/d3-format@npm:*": + version: 3.0.4 + resolution: "@types/d3-format@npm:3.0.4" + checksum: 10c0/3ac1600bf9061a59a228998f7cd3f29e85cbf522997671ba18d4d84d10a2a1aff4f95aceb143fa9960501c3ec351e113fc75884e6a504ace44dc1744083035ee + languageName: node + linkType: hard + +"@types/d3-geo@npm:*": + version: 3.1.0 + resolution: "@types/d3-geo@npm:3.1.0" + dependencies: + "@types/geojson": "npm:*" + checksum: 10c0/3745a93439038bb5b0b38facf435f7079812921d46406f5d38deaee59e90084ff742443c7ea0a8446df81a0d81eaf622fe7068cf4117a544bd4aa3b2dc182f88 + languageName: node + linkType: hard + +"@types/d3-hierarchy@npm:*": + version: 3.1.7 + resolution: "@types/d3-hierarchy@npm:3.1.7" + checksum: 10c0/873711737d6b8e7b6f1dda0bcd21294a48f75024909ae510c5d2c21fad2e72032e0958def4d9f68319d3aaac298ad09c49807f8bfc87a145a82693b5208613c7 + languageName: node + linkType: hard + +"@types/d3-interpolate@npm:*": + version: 3.0.4 + resolution: "@types/d3-interpolate@npm:3.0.4" + dependencies: + "@types/d3-color": "npm:*" + checksum: 10c0/066ebb8da570b518dd332df6b12ae3b1eaa0a7f4f0c702e3c57f812cf529cc3500ec2aac8dc094f31897790346c6b1ebd8cd7a077176727f4860c2b181a65ca4 + languageName: node + linkType: hard + +"@types/d3-path@npm:*": + version: 3.1.1 + resolution: "@types/d3-path@npm:3.1.1" + checksum: 10c0/2c36eb31ebaf2ce4712e793fd88087117976f7c4ed69cc2431825f999c8c77cca5cea286f3326432b770739ac6ccd5d04d851eb65e7a4dbcc10c982b49ad2c02 + languageName: node + linkType: hard + +"@types/d3-polygon@npm:*": + version: 3.0.2 + resolution: "@types/d3-polygon@npm:3.0.2" + checksum: 10c0/f46307bb32b6c2aef8c7624500e0f9b518de8f227ccc10170b869dc43e4c542560f6c8d62e9f087fac45e198d6e4b623e579c0422e34c85baf56717456d3f439 + languageName: node + linkType: hard + +"@types/d3-quadtree@npm:*": + version: 3.0.6 + resolution: "@types/d3-quadtree@npm:3.0.6" + checksum: 10c0/7eaa0a4d404adc856971c9285e1c4ab17e9135ea669d847d6db7e0066126a28ac751864e7ce99c65d526e130f56754a2e437a1617877098b3bdcc3ef23a23616 + languageName: node + linkType: hard + +"@types/d3-random@npm:*": + version: 3.0.3 + resolution: "@types/d3-random@npm:3.0.3" + checksum: 10c0/5f4fea40080cd6d4adfee05183d00374e73a10c530276a6455348983dda341003a251def28565a27c25d9cf5296a33e870e397c9d91ff83fb7495a21c96b6882 + languageName: node + linkType: hard + +"@types/d3-scale-chromatic@npm:*": + version: 3.1.0 + resolution: "@types/d3-scale-chromatic@npm:3.1.0" + checksum: 10c0/93c564e02d2e97a048e18fe8054e4a935335da6ab75a56c3df197beaa87e69122eef0dfbeb7794d4a444a00e52e3123514ee27cec084bd21f6425b7037828cc2 + languageName: node + linkType: hard + +"@types/d3-scale@npm:*": + version: 4.0.9 + resolution: "@types/d3-scale@npm:4.0.9" + dependencies: + "@types/d3-time": "npm:*" + checksum: 10c0/4ac44233c05cd50b65b33ecb35d99fdf07566bcdbc55bc1306b2f27d1c5134d8c560d356f2c8e76b096e9125ffb8d26d95f78d56e210d1c542cb255bdf31d6c8 + languageName: node + linkType: hard + +"@types/d3-selection@npm:*": + version: 3.0.11 + resolution: "@types/d3-selection@npm:3.0.11" + checksum: 10c0/0c512956c7503ff5def4bb32e0c568cc757b9a2cc400a104fc0f4cfe5e56d83ebde2a97821b6f2cb26a7148079d3b86a2f28e11d68324ed311cf35c2ed980d1d + languageName: node + linkType: hard + +"@types/d3-shape@npm:*": + version: 3.1.7 + resolution: "@types/d3-shape@npm:3.1.7" + dependencies: + "@types/d3-path": "npm:*" + checksum: 10c0/38e59771c1c4c83b67aa1f941ce350410522a149d2175832fdc06396b2bb3b2c1a2dd549e0f8230f9f24296ee5641a515eaf10f55ee1ef6c4f83749e2dd7dcfd + languageName: node + linkType: hard + +"@types/d3-time-format@npm:*": + version: 4.0.3 + resolution: "@types/d3-time-format@npm:4.0.3" + checksum: 10c0/9ef5e8e2b96b94799b821eed5d61a3d432c7903247966d8ad951b8ce5797fe46554b425cb7888fa5bf604b4663c369d7628c0328ffe80892156671c58d1a7f90 + languageName: node + linkType: hard + +"@types/d3-time@npm:*": + version: 3.0.4 + resolution: "@types/d3-time@npm:3.0.4" + checksum: 10c0/6d9e2255d63f7a313a543113920c612e957d70da4fb0890931da6c2459010291b8b1f95e149a538500c1c99e7e6c89ffcce5554dd29a31ff134a38ea94b6d174 + languageName: node + linkType: hard + +"@types/d3-timer@npm:*": + version: 3.0.2 + resolution: "@types/d3-timer@npm:3.0.2" + checksum: 10c0/c644dd9571fcc62b1aa12c03bcad40571553020feeb5811f1d8a937ac1e65b8a04b759b4873aef610e28b8714ac71c9885a4d6c127a048d95118f7e5b506d9e1 + languageName: node + linkType: hard + +"@types/d3-transition@npm:*": + version: 3.0.9 + resolution: "@types/d3-transition@npm:3.0.9" + dependencies: + "@types/d3-selection": "npm:*" + checksum: 10c0/4f68b9df7ac745b3491216c54203cbbfa0f117ae4c60e2609cdef2db963582152035407fdff995b10ee383bae2f05b7743493f48e1b8e46df54faa836a8fb7b5 + languageName: node + linkType: hard + +"@types/d3-zoom@npm:*": + version: 3.0.8 + resolution: "@types/d3-zoom@npm:3.0.8" + dependencies: + "@types/d3-interpolate": "npm:*" + "@types/d3-selection": "npm:*" + checksum: 10c0/1dbdbcafddcae12efb5beb6948546963f29599e18bc7f2a91fb69cc617c2299a65354f2d47e282dfb86fec0968406cd4fb7f76ba2d2fb67baa8e8d146eb4a547 + languageName: node + linkType: hard + +"@types/d3@npm:^7.4.3": + version: 7.4.3 + resolution: "@types/d3@npm:7.4.3" + dependencies: + "@types/d3-array": "npm:*" + "@types/d3-axis": "npm:*" + "@types/d3-brush": "npm:*" + "@types/d3-chord": "npm:*" + "@types/d3-color": "npm:*" + "@types/d3-contour": "npm:*" + "@types/d3-delaunay": "npm:*" + "@types/d3-dispatch": "npm:*" + "@types/d3-drag": "npm:*" + "@types/d3-dsv": "npm:*" + "@types/d3-ease": "npm:*" + "@types/d3-fetch": "npm:*" + "@types/d3-force": "npm:*" + "@types/d3-format": "npm:*" + "@types/d3-geo": "npm:*" + "@types/d3-hierarchy": "npm:*" + "@types/d3-interpolate": "npm:*" + "@types/d3-path": "npm:*" + "@types/d3-polygon": "npm:*" + "@types/d3-quadtree": "npm:*" + "@types/d3-random": "npm:*" + "@types/d3-scale": "npm:*" + "@types/d3-scale-chromatic": "npm:*" + "@types/d3-selection": "npm:*" + "@types/d3-shape": "npm:*" + "@types/d3-time": "npm:*" + "@types/d3-time-format": "npm:*" + "@types/d3-timer": "npm:*" + "@types/d3-transition": "npm:*" + "@types/d3-zoom": "npm:*" + checksum: 10c0/a9c6d65b13ef3b42c87f2a89ea63a6d5640221869f97d0657b0cb2f1dac96a0f164bf5605643c0794e0de3aa2bf05df198519aaf15d24ca135eb0e8bd8a9d879 + languageName: node + linkType: hard + +"@types/debug@npm:^4.0.0": + version: 4.1.12 + resolution: "@types/debug@npm:4.1.12" + dependencies: + "@types/ms": "npm:*" + checksum: 10c0/5dcd465edbb5a7f226e9a5efd1f399c6172407ef5840686b73e3608ce135eeca54ae8037dcd9f16bdb2768ac74925b820a8b9ecc588a58ca09eca6acabe33e2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/dompurify@npm:^3.2.0": + version: 3.2.0 + resolution: "@types/dompurify@npm:3.2.0" + dependencies: + dompurify: "npm:*" + checksum: 10c0/e83fec586ffbb1b43f7c8479f2a99c223814d240db912d53fb126aaeea52a4091deceeb75632b5d76004d88ef0fbf444984887b121dece6fa69f4172ed4b2b07 + languageName: node + linkType: hard + +"@types/estree-jsx@npm:^1.0.0": + version: 1.0.5 + resolution: "@types/estree-jsx@npm:1.0.5" + dependencies: + "@types/estree": "npm:*" + checksum: 10c0/07b354331516428b27a3ab99ee397547d47eb223c34053b48f84872fafb841770834b90cc1a0068398e7c7ccb15ec51ab00ec64b31dc5e3dbefd624638a35c6d + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:^1.0.0": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/gensync@npm:^1.0.0": + version: 1.0.4 + resolution: "@types/gensync@npm:1.0.4" + checksum: 10c0/1daeb1693196a85ee68b82f3fb30906a1cccede69d492b190de80ff20cec2d528d98cad866d733fd83cb171096dfe8c26c9c02c50ffb93e1113d48bd79daa556 + languageName: node + linkType: hard + +"@types/geojson-vt@npm:3.2.5": + version: 3.2.5 + resolution: "@types/geojson-vt@npm:3.2.5" + dependencies: + "@types/geojson": "npm:*" + checksum: 10c0/bfd9157c7d0441dc4b420e0c6df65b4e4b29f3d33cc77667b3dc5acd68ba6326bfbfd867642645357382e3374ceebfb9c5d15f2b2c0ee3e3c492e0b5a2bb71be + languageName: node + linkType: hard + +"@types/geojson@npm:*, @types/geojson@npm:^7946.0.10, @types/geojson@npm:^7946.0.14": + version: 7946.0.16 + resolution: "@types/geojson@npm:7946.0.16" + checksum: 10c0/1ff24a288bd5860b766b073ead337d31d73bdc715e5b50a2cee5cb0af57a1ed02cc04ef295f5fa68dc40fe3e4f104dd31282b2b818a5ba3231bc1001ba084e3c + languageName: node + linkType: hard + +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/235d2fc69741448e853333b7c3d1180a966dd2b8972c8cbcd6b2a0c6cd7f8d582ab2b8e58219dbc62cce8f1b40aa317ff78ea2201cdd8249da5025adebed6f0b + languageName: node + linkType: hard + +"@types/hast@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/3249781a511b38f1d330fd1e3344eed3c4e7ea8eff82e835d35da78e637480d36fad37a78be5a7aed8465d237ad0446abc1150859d0fde395354ea634decf9f7 + languageName: node + linkType: hard + +"@types/history@npm:^4.7.11": + version: 4.7.11 + resolution: "@types/history@npm:4.7.11" + checksum: 10c0/3facf37c2493d1f92b2e93a22cac7ea70b06351c2ab9aaceaa3c56aa6099fb63516f6c4ec1616deb5c56b4093c026a043ea2d3373e6c0644d55710364d02c934 + languageName: node + linkType: hard + +"@types/hoist-non-react-statics@npm:*": + version: 3.3.6 + resolution: "@types/hoist-non-react-statics@npm:3.3.6" + dependencies: + "@types/react": "npm:*" + hoist-non-react-statics: "npm:^3.3.0" + checksum: 10c0/149a4c217d81f21f8a1e152160a59d5b99b6a9aa6d354385d5f5bc02760cbf1e170a8442ba92eb653befff44b0c5bc2234bb77ce33e0d11a65f779e8bab5c321 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.6 + resolution: "@types/istanbul-lib-coverage@npm:2.0.6" + checksum: 10c0/3948088654f3eeb45363f1db158354fb013b362dba2a5c2c18c559484d5eb9f6fd85b23d66c0a7c2fcfab7308d0a585b14dadaca6cc8bf89ebfdc7f8f5102fb7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.3 + resolution: "@types/istanbul-lib-report@npm:3.0.3" + dependencies: + "@types/istanbul-lib-coverage": "npm:*" + checksum: 10c0/247e477bbc1a77248f3c6de5dadaae85ff86ac2d76c5fc6ab1776f54512a745ff2a5f791d22b942e3990ddbd40f3ef5289317c4fca5741bedfaa4f01df89051c + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/istanbul-reports@npm:3.0.4" + dependencies: + "@types/istanbul-lib-report": "npm:*" + checksum: 10c0/1647fd402aced5b6edac87274af14ebd6b3a85447ef9ad11853a70fd92a98d35f81a5d3ea9fcb5dbb5834e800c6e35b64475e33fcae6bfa9acc70d61497c54ee + languageName: node + linkType: hard + +"@types/jest@npm:^29.5.14": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10c0/18e0712d818890db8a8dab3d91e9ea9f7f19e3f83c2e50b312f557017dc81466207a71f3ed79cf4428e813ba939954fa26ffa0a9a7f153181ba174581b1c2aed + languageName: node + linkType: hard + +"@types/js-yaml@npm:^4.0.9": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: 10c0/24de857aa8d61526bbfbbaa383aa538283ad17363fcd5bb5148e2c7f604547db36646440e739d78241ed008702a8920665d1add5618687b6743858fae00da211 + languageName: node + linkType: hard + +"@types/jsdom@npm:^20.0.0": + version: 20.0.1 + resolution: "@types/jsdom@npm:20.0.1" + dependencies: + "@types/node": "npm:*" + "@types/tough-cookie": "npm:*" + parse5: "npm:^7.0.0" + checksum: 10c0/3d4b2a3eab145674ee6da482607c5e48977869109f0f62560bf91ae1a792c9e847ac7c6aaf243ed2e97333cb3c51aef314ffa54a19ef174b8f9592dfcb836b25 + languageName: node + linkType: hard + +"@types/lodash@npm:^4.17.24": + version: 4.17.24 + resolution: "@types/lodash@npm:4.17.24" + checksum: 10c0/b72f60d4daacdad1fa643edb3faba204c02a01eb1ac00a83ff73496a6d236fc55e459c06106e8ced42277dba932d087d8fc090f8de4ef590d3f91e6d6f7ce85a + languageName: node + linkType: hard + +"@types/luxon@npm:^3.7.1": + version: 3.7.1 + resolution: "@types/luxon@npm:3.7.1" + checksum: 10c0/2db30c13b58adcd86daa447faa3ba59515fe907ead8ee3e6bb716d662812af0619d712f6c1eb190cdd7f9d2c00444c3ecd80af0f36e8143eb0c5e7339d6b2aca + languageName: node + linkType: hard + +"@types/mapbox__point-geometry@npm:*, @types/mapbox__point-geometry@npm:^0.1.4": + version: 0.1.4 + resolution: "@types/mapbox__point-geometry@npm:0.1.4" + checksum: 10c0/670191664ea0a6ccb4563500fe815a9aba029ba2f0528d42f9eb560ccb44f6542ba8674e2a3f6d41bd10ad8855b4df4782b5340c980ca182ef9fe6752f2737b8 + languageName: node + linkType: hard + +"@types/mapbox__vector-tile@npm:^1.3.4": + version: 1.3.4 + resolution: "@types/mapbox__vector-tile@npm:1.3.4" + dependencies: + "@types/geojson": "npm:*" + "@types/mapbox__point-geometry": "npm:*" + "@types/pbf": "npm:*" + checksum: 10c0/082907ed9cf96b82327dabf3b4c3a14746a825e4a81f0abf46b50e2557f25cbda652725d8af002e5edcc344a83c85e1a4b71a2d39ef4d829c243344a85ac13a6 + languageName: node + linkType: hard + +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" + dependencies: + "@types/unist": "npm:*" + checksum: 10c0/84f403dbe582ee508fd9c7643ac781ad8597fcbfc9ccb8d4715a2c92e4545e5772cbd0dbdf18eda65789386d81b009967fdef01b24faf6640f817287f54d9c82 + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10c0/5ce692ffe1549e1b827d99ef8ff71187457e0eb44adbae38fdf7b9a74bae8d20642ee963c14516db1d35fa2652e65f47680fdf679dcbde52bbfadd021f497225 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 22.13.1 + resolution: "@types/node@npm:22.13.1" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 10c0/d4e56d41d8bd53de93da2651c0a0234e330bd7b1b6d071b1a94bd3b5ee2d9f387519e739c52a15c1faa4fb9d97e825b848421af4b2e50e6518011e7adb4a34b7 + languageName: node + linkType: hard + +"@types/node@npm:^25.6.0": + version: 25.6.0 + resolution: "@types/node@npm:25.6.0" + dependencies: + undici-types: "npm:~7.19.0" + checksum: 10c0/d2d2015630ff098a201407f55f5077a20270ae4f465c739b40865cd9933b91b9c5d2b85568eadaf3db0801b91e267333ca7eb39f007428b173d1cdab4b339ac5 + languageName: node + linkType: hard + +"@types/pbf@npm:*, @types/pbf@npm:^3.0.5": + version: 3.0.5 + resolution: "@types/pbf@npm:3.0.5" + checksum: 10c0/c32348c6c81e6c31fe4a1f59983e3a9904727b809fb1e5ddec4fad49abaf93070ec26ee0c04c6516536c181c945b3c7d9e226549eaac3b2e12cb7b57f549a49c + languageName: node + linkType: hard + +"@types/plotly.js@npm:*": + version: 2.35.2 + resolution: "@types/plotly.js@npm:2.35.2" + checksum: 10c0/7b7b6d7ee5a5393f4cb42ed1e67b8f72624f8e6d4aedb778b15481e2b028bcafb520d549ed3eb2d05693523fc2b3e7b7252ef6f661c9c1d97d4c527428dc1ba7 + languageName: node + linkType: hard + +"@types/react-dom@npm:^19.2.3": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 + languageName: node + linkType: hard + +"@types/react-plotly.js@npm:^2.6.4": + version: 2.6.4 + resolution: "@types/react-plotly.js@npm:2.6.4" + dependencies: + "@types/plotly.js": "npm:*" + "@types/react": "npm:*" + checksum: 10c0/e1483e77b2162a7284b186665131a053560a84293c85ade16cd65039383c3894550397cf2b599ce1c870521622f81b176ddbdbe7a10da937b3b88e4aa4b90d4a + languageName: node + linkType: hard + +"@types/react-router-dom@npm:^5.3.3": + version: 5.3.3 + resolution: "@types/react-router-dom@npm:5.3.3" + dependencies: + "@types/history": "npm:^4.7.11" + "@types/react": "npm:*" + "@types/react-router": "npm:*" + checksum: 10c0/a9231a16afb9ed5142678147eafec9d48582809295754fb60946e29fcd3757a4c7a3180fa94b45763e4c7f6e3f02379e2fcb8dd986db479dcab40eff5fc62a91 + languageName: node + linkType: hard + +"@types/react-router@npm:*": + version: 5.1.20 + resolution: "@types/react-router@npm:5.1.20" + dependencies: + "@types/history": "npm:^4.7.11" + "@types/react": "npm:*" + checksum: 10c0/1f7eee61981d2f807fa01a34a0ef98ebc0774023832b6611a69c7f28fdff01de5a38cabf399f32e376bf8099dcb7afaf724775bea9d38870224492bea4cb5737 + languageName: node + linkType: hard + +"@types/react@npm:*": + version: 19.0.8 + resolution: "@types/react@npm:19.0.8" + dependencies: + csstype: "npm:^3.0.2" + checksum: 10c0/5fa7236356b1476de03519c66ef65d4fd904826956105619e2ad60cb0b55ae7b251dd5fff02234076225b5e15333d0d936bf9dbe1d461406f8a2ba01c197ddcd + languageName: node + linkType: hard + +"@types/react@npm:^19.2.14": + version: 19.2.14 + resolution: "@types/react@npm:19.2.14" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/7d25bf41b57719452d86d2ac0570b659210402707313a36ee612666bf11275a1c69824f8c3ee1fdca077ccfe15452f6da8f1224529b917050eb2d861e52b59b7 + languageName: node + linkType: hard + +"@types/stack-utils@npm:^2.0.0": + version: 2.0.3 + resolution: "@types/stack-utils@npm:2.0.3" + checksum: 10c0/1f4658385ae936330581bcb8aa3a066df03867d90281cdf89cc356d404bd6579be0f11902304e1f775d92df22c6dd761d4451c804b0a4fba973e06211e9bd77c + languageName: node + linkType: hard + +"@types/styled-components@npm:^5.1.36": + version: 5.1.36 + resolution: "@types/styled-components@npm:5.1.36" + dependencies: + "@types/hoist-non-react-statics": "npm:*" + "@types/react": "npm:*" + csstype: "npm:^3.2.2" + checksum: 10c0/bbbf65e22a852a5bc3e1839aa591372febe084e33b8d4f44712f790e0675df9f5a327c6c6a262bbf5fd08032afe2ac9b265de92c72efca0bfa5dea09d7127c28 + languageName: node + linkType: hard + +"@types/supercluster@npm:^7.1.3": + version: 7.1.3 + resolution: "@types/supercluster@npm:7.1.3" + dependencies: + "@types/geojson": "npm:*" + checksum: 10c0/0d55dad98df0990fc38a7bb64dc23dda46014187c0d7638e6f2b6717ba8931b13e5b1d394789833a2ac822014c977ef64623dffd81a0bbf39e52c53c8183741f + languageName: node + linkType: hard + +"@types/tough-cookie@npm:*": + version: 4.0.5 + resolution: "@types/tough-cookie@npm:4.0.5" + checksum: 10c0/68c6921721a3dcb40451543db2174a145ef915bc8bcbe7ad4e59194a0238e776e782b896c7a59f4b93ac6acefca9161fccb31d1ce3b3445cb6faa467297fb473 + languageName: node + linkType: hard + +"@types/trusted-types@npm:^2.0.7": + version: 2.0.7 + resolution: "@types/trusted-types@npm:2.0.7" + checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c + languageName: node + linkType: hard + +"@types/unist@npm:*, @types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 10c0/2b1e4adcab78388e088fcc3c0ae8700f76619dbcb4741d7d201f87e2cb346bfc29a89003cfea2d76c996e1061452e14fcd737e8b25aacf949c1f2d6b2bc3dd60 + languageName: node + linkType: hard + +"@types/unist@npm:^2.0.0": + version: 2.0.11 + resolution: "@types/unist@npm:2.0.11" + checksum: 10c0/24dcdf25a168f453bb70298145eb043cfdbb82472db0bc0b56d6d51cd2e484b9ed8271d4ac93000a80da568f2402e9339723db262d0869e2bf13bc58e081768d + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.3 + resolution: "@types/yargs-parser@npm:21.0.3" + checksum: 10c0/e71c3bd9d0b73ca82e10bee2064c384ab70f61034bbfb78e74f5206283fc16a6d85267b606b5c22cb2a3338373586786fed595b2009825d6a9115afba36560a0 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.33 + resolution: "@types/yargs@npm:17.0.33" + dependencies: + "@types/yargs-parser": "npm:*" + checksum: 10c0/d16937d7ac30dff697801c3d6f235be2166df42e4a88bf730fa6dc09201de3727c0a9500c59a672122313341de5f24e45ee0ff579c08ce91928e519090b7906b + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.0.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a + languageName: node + linkType: hard + +"@upsetjs/venn.js@npm:^2.0.0": + version: 2.0.0 + resolution: "@upsetjs/venn.js@npm:2.0.0" + dependencies: + d3-selection: "npm:^3.0.0" + d3-transition: "npm:^3.0.1" + dependenciesMeta: + d3-selection: + optional: true + d3-transition: + optional: true + checksum: 10c0/b12014d94708ab4df7f5a4b6205c6f23ff235cca2ffe91df3314862b109b826e52f9020c2a2f7527d3712d21c578d6db9cdb60ce46a528739cc18e58d111f724 + languageName: node + linkType: hard + +"@vitejs/plugin-react-swc@npm:^4.3.0": + version: 4.3.0 + resolution: "@vitejs/plugin-react-swc@npm:4.3.0" + dependencies: + "@rolldown/pluginutils": "npm:1.0.0-rc.7" + "@swc/core": "npm:^1.15.11" + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 || ^8 + checksum: 10c0/36228152f549e0db226ad59a41fcd4fc3df636575637c9190d5927081162f2c0f592756aa11d4fd4a7e34c734b911b319b0e87570f73b1cd67d7a38d8cbff80d + languageName: node + linkType: hard + +"@vitest/expect@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/expect@npm:4.1.5" + dependencies: + "@standard-schema/spec": "npm:^1.1.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/5184682304db471aa20024c1154210ad3d6d590afb61646201ce1a15297259f9a35f92f8fad4435bc8a82135e307ddd27c8495f72417d72d9aa139eb281d9e06 + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/mocker@npm:4.1.5" + dependencies: + "@vitest/spy": "npm:4.1.5" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/bcfe97700476130933c7ea33fa670c8d2768a81de5325ce407f901e55c2f66cabbb88a7b6cffb46ddf33dff7d8fc209d769fb298f568e310fbeead9b36f6fdb9 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/pretty-format@npm:4.1.5" + dependencies: + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/42b5e9b75e87c0a884d36bee364e2d07ee45e96f413377737a74993e077d90c3a12aa36743855aee5e4e28b78fae20e3e6de5eef8d5344b9aba2bc1e1d5537a1 + languageName: node + linkType: hard + +"@vitest/runner@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/runner@npm:4.1.5" + dependencies: + "@vitest/utils": "npm:4.1.5" + pathe: "npm:^2.0.3" + checksum: 10c0/6a03b313a121155f6dd9e32eeb103c0e12440f586bc4ba1f0d77444e44c6df4652a44443718552037463115635b8378e11f35902d90ce1326f77743219fca056 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/snapshot@npm:4.1.5" + dependencies: + "@vitest/pretty-format": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/e11bf50d06702331290750a40eaef86078c108df3cd9a52bb1be7b84250048790163f36827525be6a383a4bb1994fc35e6d0c24239a41688b0bb68a1d15d172f + languageName: node + linkType: hard + +"@vitest/spy@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/spy@npm:4.1.5" + checksum: 10c0/fda6b1ee0a2fec1a152d8041aba7a79744c3876863b244d1ed406d02b36e8ccc997edb2e3963d1027d728d3dc5a33813e11bef53a0a14fc7de4de5e721d0f591 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.1.5": + version: 4.1.5 + resolution: "@vitest/utils@npm:4.1.5" + dependencies: + "@vitest/pretty-format": "npm:4.1.5" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/72409717e68018e5fe42fa173cc4eff6def8c35bd52013f86ddb414cd28d73fcc425ac62968e01a52371b3fd5a7a775536283d2f1d64432753f628712a6a4908 + languageName: node + linkType: hard + +"@zeit/schemas@npm:2.36.0": + version: 2.36.0 + resolution: "@zeit/schemas@npm:2.36.0" + checksum: 10c0/858c3ae46d23122f65d576013dc74f120af0ca7f3256c4b7077bcd12e952c8f71d8241a5165c23d18f6378e198a1db7e93bc8fae8ed0769e4cf4e2df953ee955 + languageName: node + linkType: hard + +"abab@npm:^2.0.6": + version: 2.0.6 + resolution: "abab@npm:2.0.6" + checksum: 10c0/0b245c3c3ea2598fe0025abf7cc7bb507b06949d51e8edae5d12c1b847a0a0c09639abcb94788332b4e2044ac4491c1e8f571b51c7826fd4b0bda1685ad4a278 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"abs-svg-path@npm:^0.1.1, abs-svg-path@npm:~0.1.1": + version: 0.1.1 + resolution: "abs-svg-path@npm:0.1.1" + checksum: 10c0/aa763f3843cd4d7c3eabcddc91834ab27def4c1f470b98a3bf01ebe82928629c5aeaa97766252781449e0c722e1785f8e512fea79f86d3d10f8eca220d6aa292 + languageName: node + linkType: hard + +"acorn-globals@npm:^7.0.0": + version: 7.0.1 + resolution: "acorn-globals@npm:7.0.1" + dependencies: + acorn: "npm:^8.1.0" + acorn-walk: "npm:^8.0.2" + checksum: 10c0/7437f58e92d99292dbebd0e79531af27d706c9f272f31c675d793da6c82d897e75302a8744af13c7f7978a8399840f14a353b60cf21014647f71012982456d2b + languageName: node + linkType: hard + +"acorn-walk@npm:^8.0.2, acorn-walk@npm:^8.1.1": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10c0/76537ac5fb2c37a64560feaf3342023dadc086c46da57da363e64c6148dc21b57d49ace26f949e225063acb6fb441eabffd89f7a3066de5ad37ab3e328927c62 + languageName: node + linkType: hard + +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 10c0/bd0b2c2b0f334bbee48828ff897c12bd2eb5898d03bf556dcc8942022cec795ac5bb5b6b585e2de687db6231faf07e096b59a361231dd8c9344d5df5f7f0e526 + languageName: node + linkType: hard + +"acorn@npm:^8.1.0, acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.8.1": + version: 8.14.0 + resolution: "acorn@npm:8.14.0" + bin: + acorn: bin/acorn + checksum: 10c0/6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7 + languageName: node + linkType: hard + +"acorn@npm:^8.16.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: 10c0/c9c52697227661b68d0debaf972222d4f622aa06b185824164e153438afa7b08273432ca43ea792cadb24dada1d46f6f6bb1ef8de9956979288cc1b96bf9914e + languageName: node + linkType: hard + +"adler-32@npm:~1.3.0": + version: 1.3.1 + resolution: "adler-32@npm:1.3.1" + checksum: 10c0/c1b7185526ee1bbe0eac8ed414d5226af4cd02a0540449a72ec1a75f198c5e93352ba4d7b9327231eea31fd83c2d080d13baf16d8ed5710fb183677beb85f612 + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"ajv@npm:8.18.0": + version: 8.18.0 + resolution: "ajv@npm:8.18.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/e7517c426173513a07391be951879932bdf3348feaebd2199f5b901c20f99d60db8cd1591502d4d551dc82f594e82a05c4fe1c70139b15b8937f7afeaed9532f + languageName: node + linkType: hard + +"ansi-align@npm:^3.0.1": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: "npm:^4.1.0" + checksum: 10c0/ad8b755a253a1bc8234eb341e0cec68a857ab18bf97ba2bda529e86f6e30460416523e0ec58c32e5c21f0ca470d779503244892873a5895dbd0c39c788e82467 + languageName: node + linkType: hard + +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: "npm:^0.21.3" + checksum: 10c0/da917be01871525a3dfcf925ae2977bc59e8c513d4423368645634bf5d4ceba5401574eb705c1e92b79f7292af5a656f78c5725a4b0e1cec97c4b413705c1d50 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10c0/9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: 10c0/a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: 10c0/9c4ca80eb3c2fb7b33841c210d2f20807f40865d27008d7c3f707b7f95cab7d67462a565e2388ac3285b71cb3d9bb2173de8da37c57692a362885ec34d6e27df + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 10c0/5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10c0/57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac + languageName: node + linkType: hard + +"arch@npm:^2.2.0": + version: 2.2.0 + resolution: "arch@npm:2.2.0" + checksum: 10c0/4ceaf8d8207817c216ebc4469742052cb0a097bc45d9b7fcd60b7507220da545a28562ab5bdd4dfe87921bb56371a0805da4e10d704e01f93a15f83240f1284c + languageName: node + linkType: hard + +"arg@npm:5.0.2": + version: 5.0.2 + resolution: "arg@npm:5.0.2" + checksum: 10c0/ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10c0/070ff801a9d236a6caa647507bdcc7034530604844d64408149a26b9e87c2f97650055c0f049abd1efc024b334635c01f29e0b632b371ac3f26130f4cf65997a + languageName: node + linkType: hard + +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: "npm:~1.0.2" + checksum: 10c0/b2972c5c23c63df66bca144dbc65d180efa74f25f8fd9b7d9a0a6c88ae839db32df3d54770dcb6460cf840d232b60695d1a6b1053f599d84e73f7437087712de + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"aria-hidden@npm:^1.2.3": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10c0/7720cb539497a9f760f68f98a4b30f22c6767aa0e72fa7d58279f7c164e258fc38b2699828f8de881aab0fc8e9c56d1313a3f1a965046fc0381a554dbc72b54a + languageName: node + linkType: hard + +"aria-query@npm:5.3.0": + version: 5.3.0 + resolution: "aria-query@npm:5.3.0" + dependencies: + dequal: "npm:^2.0.3" + checksum: 10c0/2bff0d4eba5852a9dd578ecf47eaef0e82cc52569b48469b0aac2db5145db0b17b7a58d9e01237706d1e14b7a1b0ac9b78e9c97027ad97679dd8f91b85da1469 + languageName: node + linkType: hard + +"array-bounds@npm:^1.0.0, array-bounds@npm:^1.0.1": + version: 1.0.1 + resolution: "array-bounds@npm:1.0.1" + checksum: 10c0/69ee3bb14b6a9e170ef052d21354e0b1d63fd113702e082b70fc08197767f36018cf86d532d9602e01ae76fb36dbafbe72a194f5bd61312f5626db2aff305a2e + languageName: node + linkType: hard + +"array-find-index@npm:^1.0.2": + version: 1.0.2 + resolution: "array-find-index@npm:1.0.2" + checksum: 10c0/86b9485c74ddd324feab807e10a6de3f9c1683856267236fac4bb4d4667ada6463e106db3f6c540ae6b720e0442b590ec701d13676df4c6af30ebf4da09b4f57 + languageName: node + linkType: hard + +"array-normalize@npm:^1.1.4": + version: 1.1.4 + resolution: "array-normalize@npm:1.1.4" + dependencies: + array-bounds: "npm:^1.0.0" + checksum: 10c0/68ef2d37105ab84101ee024b8d660f6b820faedd2c89a018d68e1e774a2cc31b21060bb5e168b233919d50127857fbf62e2f8d20ae8e0d428b6c40d393454ee9 + languageName: node + linkType: hard + +"array-range@npm:^1.0.1": + version: 1.0.1 + resolution: "array-range@npm:1.0.1" + checksum: 10c0/d54a5cf60ac91c525c3ad52b6b02994c0bb437a175852ef64fd5474888c56053a97e108aeb32bc5fc17b928783d57dddf87757752eb5f0aa9092566a731fa734 + languageName: node + linkType: hard + +"array-rearrange@npm:^2.2.2": + version: 2.2.2 + resolution: "array-rearrange@npm:2.2.2" + checksum: 10c0/3cee3171eaeb364110dba1d9f1ba80c5b37d8284465930482060240097c47bbcc89b659748c515fdf3ac23fc230658a08847c8507c9ffae4f17fa74218716ef2 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"axios@npm:^1.15.2": + version: 1.15.2 + resolution: "axios@npm:1.15.2" + dependencies: + follow-redirects: "npm:^1.15.11" + form-data: "npm:^4.0.5" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/4eeae0feeaa7fdc1ef24f81f8b378fdadedf4aebdd6bf224484675160f8744cf17b9b0d1c215279979940f7e8ce463beffa2f713099612e428eac238515c81d5 + languageName: node + linkType: hard + +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10c0/2eda9c1391e51936ca573dd1aedfee07b14c59b33dbe16ef347873ddd777bcf6e2fc739681e9e9661ab54ef84a3109a03725be2ac32cd2124c07ea4401cbe8c1 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10c0/1075657feb705e00fd9463b329921856d3775d9867c5054b449317d39153f8fbcebd3e02ebf00432824e647faff3683a9ca0a941325ef1afe9b3c4dd51b24beb + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10c0/7e6451caaf7dce33d010b8aafb970e62f1b0c0b57f4978c37b0d457bbcf0874d75a395a102daf0bae0bd14eafb9f6e9a165ee5e899c0a4f1f3bb2e07b304ed2e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.1.0 + resolution: "babel-preset-current-node-syntax@npm:1.1.0" + dependencies: + "@babel/plugin-syntax-async-generators": "npm:^7.8.4" + "@babel/plugin-syntax-bigint": "npm:^7.8.3" + "@babel/plugin-syntax-class-properties": "npm:^7.12.13" + "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" + "@babel/plugin-syntax-import-attributes": "npm:^7.24.7" + "@babel/plugin-syntax-import-meta": "npm:^7.10.4" + "@babel/plugin-syntax-json-strings": "npm:^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" + "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" + "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" + "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" + "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" + "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/0b838d4412e3322cb4436f246e24e9c00bebcedfd8f00a2f51489db683bd35406bbd55a700759c28d26959c6e03f84dd6a1426f576f440267c1d7a73c5717281 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/ec5fd0276b5630b05f0c14bb97cc3815c6b31600c683ebb51372e54dcb776cff790bdeeabd5b8d01ede375a040337ccbf6a3ccd68d3a34219125945e167ad943 + languageName: node + linkType: hard + +"bail@npm:^2.0.0": + version: 2.0.2 + resolution: "bail@npm:2.0.2" + checksum: 10c0/25cbea309ef6a1f56214187004e8f34014eb015713ea01fa5b9b7e9e776ca88d0fdffd64143ac42dc91966c915a4b7b683411b56e14929fad16153fc026ffb8b + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-arraybuffer@npm:^1.0.2": + version: 1.0.2 + resolution: "base64-arraybuffer@npm:1.0.2" + checksum: 10c0/3acac95c70f9406e87a41073558ba85b6be9dbffb013a3d2a710e3f2d534d506c911847d5d9be4de458af6362c676de0a5c4c2d7bdf4def502d00b313368e72f + languageName: node + linkType: hard + +"binary-search-bounds@npm:^2.0.4": + version: 2.0.5 + resolution: "binary-search-bounds@npm:2.0.5" + checksum: 10c0/d433db5fba086d1585e268c0b0b7ae6ec359df60b9135112d1fd4dcf2f8a8820caab7e2523bbbca0a9cae0725486f67a6663a87b5637f0e58f4201985e6e5b0b + languageName: node + linkType: hard + +"bit-twiddle@npm:^1.0.0, bit-twiddle@npm:^1.0.2": + version: 1.0.2 + resolution: "bit-twiddle@npm:1.0.2" + checksum: 10c0/edd86fdaeb27fb5acb9dbde247a71e511ebb6c5406ed645038974203dce8c87ef0359f5d5b60212c2a54d0a52ab16a27c4cc3b1cd4e06256a33881ed77f03d7a + languageName: node + linkType: hard + +"bitmap-sdf@npm:^1.0.0": + version: 1.0.4 + resolution: "bitmap-sdf@npm:1.0.4" + checksum: 10c0/1af490ff70c6fcb4f2a3d6c2217c0876a63a8a164515ee7283622409f35c050827ec1ad6fa8a1f152bb288d77de8fe7f7858ce67f1e7621af56de8755dce48fd + languageName: node + linkType: hard + +"bl@npm:^2.2.1": + version: 2.2.1 + resolution: "bl@npm:2.2.1" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10c0/37481260f1661755253b6205fcdd64b6d852147aaf61628947d2193fcdb78e625ee061dae0094ac16e7c7f10b12c90110fb2b08826815d47a28f9628bebb5a8f + languageName: node + linkType: hard + +"boxen@npm:7.0.0": + version: 7.0.0 + resolution: "boxen@npm:7.0.0" + dependencies: + ansi-align: "npm:^3.0.1" + camelcase: "npm:^7.0.0" + chalk: "npm:^5.0.1" + cli-boxes: "npm:^3.0.0" + string-width: "npm:^5.1.2" + type-fest: "npm:^2.13.0" + widest-line: "npm:^4.0.1" + wrap-ansi: "npm:^8.0.1" + checksum: 10c0/af5e8bc3f1486ac50ec7485ae482eb1d4db905233d7ab2acafc406b576375be85bdc60b53fab99c842c42c274328b7219c7ae79adab13161f4c84e139f4b06ae + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.14 + resolution: "brace-expansion@npm:1.1.14" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/b6fdac832bc4e36a753658c9ed052c2e1a2be221763b002df25d1efbf7d21724334e726a6cd5eadc72a4b19ec3efb632d629cc003bc9c62f7af7a7915ffa4385 + languageName: node + linkType: hard + +"braces@npm:^3.0.3": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.24.4 + resolution: "browserslist@npm:4.24.4" + dependencies: + caniuse-lite: "npm:^1.0.30001688" + electron-to-chromium: "npm:^1.5.73" + node-releases: "npm:^2.0.19" + update-browserslist-db: "npm:^1.1.1" + bin: + browserslist: cli.js + checksum: 10c0/db7ebc1733cf471e0b490b4f47e3e2ea2947ce417192c9246644e92c667dd56a71406cc58f62ca7587caf828364892e9952904a02b7aead752bc65b62a37cfe9 + languageName: node + linkType: hard + +"bs-logger@npm:^0.2.6": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: "npm:2.x" + checksum: 10c0/80e89aaaed4b68e3374ce936f2eb097456a0dddbf11f75238dbd53140b1e39259f0d248a5089ed456f1158984f22191c3658d54a713982f676709fbe1a6fa5a0 + languageName: node + linkType: hard + +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: "npm:^0.4.0" + checksum: 10c0/24d8dfb7b6d457d73f32744e678a60cc553e4ec0e9e1a01cf614b44d85c3c87e188d3cc78ef0442ce5032ee6818de20a0162ba1074725c0d08908f62ea979227 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10c0/124fff9d66d691a86d3b062eff4663fe437a9d9ee4b47b1b9e97f5a5d14f6d5399345db80f796827be7c95e70a8e765dd404b7c3ff3b3324f98e9b0c8826cc34 + languageName: node + linkType: hard + +"bytes@npm:3.0.0": + version: 3.0.0 + resolution: "bytes@npm:3.0.0" + checksum: 10c0/91d42c38601c76460519ffef88371caacaea483a354c8e4b8808e7b027574436a5713337c003ea3de63ee4991c2a9a637884fdfe7f761760d746929d9e8fec60 + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + +"camelcase@npm:^7.0.0": + version: 7.0.1 + resolution: "camelcase@npm:7.0.1" + checksum: 10c0/3adfc9a0e96d51b3a2f4efe90a84dad3e206aaa81dfc664f1bd568270e1bf3b010aad31f01db16345b4ffe1910e16ab411c7273a19a859addd1b98ef7cf4cfbd + languageName: node + linkType: hard + +"camelize@npm:^1.0.0": + version: 1.0.1 + resolution: "camelize@npm:1.0.1" + checksum: 10c0/4c9ac55efd356d37ac483bad3093758236ab686192751d1c9daa43188cc5a07b09bd431eb7458a4efd9ca22424bba23253e7b353feb35d7c749ba040de2385fb + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001688": + version: 1.0.30001699 + resolution: "caniuse-lite@npm:1.0.30001699" + checksum: 10c0/e87b3a0602c3124131f6a21f1eb262378e17a2ee3089e3c472ac8b9caa85cf7d6a219655379302c29c6f10a74051f2a712639d7f98ee0444c73fefcbaf25d519 + languageName: node + linkType: hard + +"canvas-fit@npm:^1.5.0": + version: 1.5.0 + resolution: "canvas-fit@npm:1.5.0" + dependencies: + element-size: "npm:^1.1.1" + checksum: 10c0/9d85ce4c205e7edf87fa6281891780e1f62c0ffc62b4e4b16f3d27960e2b81cb05e340b21068bd35ea63651c5f343f3c0af981e40cd2f9a42bc6c7e387248bbb + languageName: node + linkType: hard + +"ccount@npm:^2.0.0": + version: 2.0.1 + resolution: "ccount@npm:2.0.1" + checksum: 10c0/3939b1664390174484322bc3f45b798462e6c07ee6384cb3d645e0aa2f318502d174845198c1561930e1d431087f74cf1fe291ae9a4722821a9f4ba67e574350 + languageName: node + linkType: hard + +"cfb@npm:~1.2.1": + version: 1.2.2 + resolution: "cfb@npm:1.2.2" + dependencies: + adler-32: "npm:~1.3.0" + crc-32: "npm:~1.2.0" + checksum: 10c0/87f6d9c3878268896ed6ca29dfe32a2aa078b12d0f21d8405c95911b74ab6296823d7312bbf5e18326d00b16cc697f587e07a17018c5edf7a1ba31dd5bc6da36 + languageName: node + linkType: hard + +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + +"chalk-template@npm:0.4.0": + version: 0.4.0 + resolution: "chalk-template@npm:0.4.0" + dependencies: + chalk: "npm:^4.1.2" + checksum: 10c0/6a4cb4252966475f0bd3ee1cd8780146e1ba69f445e59c565cab891ac18708c8143515d23e2b0fb7e192574fb7608d429ea5b28f3b7b9507770ad6fccd3467e3 + languageName: node + linkType: hard + +"chalk@npm:5.0.1": + version: 5.0.1 + resolution: "chalk@npm:5.0.1" + checksum: 10c0/97898611ae40cfdeda9778901731df1404ea49fac0eb8253804e8d21b8064917df9823e29c0c9d766aab623da1a0b43d0e072d19a73d4f62d0d9115aef4c64e6 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"chalk@npm:^5.0.1": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: 10c0/b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + languageName: node + linkType: hard + +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: 10c0/57a09a86371331e0be35d9083ba429e86c4f4648ecbe27455dbfb343037c16ee6fdc7f6b61f433a57cc5ded5561d71c56a150e018f40c2ffb7bc93a26dae341e + languageName: node + linkType: hard + +"character-entities-html4@npm:^2.0.0": + version: 2.1.0 + resolution: "character-entities-html4@npm:2.1.0" + checksum: 10c0/fe61b553f083400c20c0b0fd65095df30a0b445d960f3bbf271536ae6c3ba676f39cb7af0b4bf2755812f08ab9b88f2feed68f9aebb73bb153f7a115fe5c6e40 + languageName: node + linkType: hard + +"character-entities-legacy@npm:^3.0.0": + version: 3.0.0 + resolution: "character-entities-legacy@npm:3.0.0" + checksum: 10c0/ec4b430af873661aa754a896a2b55af089b4e938d3d010fad5219299a6b6d32ab175142699ee250640678cd64bdecd6db3c9af0b8759ab7b155d970d84c4c7d1 + languageName: node + linkType: hard + +"character-entities@npm:^2.0.0": + version: 2.0.2 + resolution: "character-entities@npm:2.0.2" + checksum: 10c0/b0c645a45bcc90ff24f0e0140f4875a8436b8ef13b6bcd31ec02cfb2ca502b680362aa95386f7815bdc04b6464d48cf191210b3840d7c04241a149ede591a308 + languageName: node + linkType: hard + +"character-reference-invalid@npm:^2.0.0": + version: 2.0.1 + resolution: "character-reference-invalid@npm:2.0.1" + checksum: 10c0/2ae0dec770cd8659d7e8b0ce24392d83b4c2f0eb4a3395c955dce5528edd4cc030a794cfa06600fcdd700b3f2de2f9b8e40e309c0011c4180e3be64a0b42e6a1 + languageName: node + linkType: hard + +"chevrotain-allstar@npm:~0.3.1": + version: 0.3.1 + resolution: "chevrotain-allstar@npm:0.3.1" + dependencies: + lodash-es: "npm:^4.17.21" + peerDependencies: + chevrotain: ^11.0.0 + checksum: 10c0/5cadedffd3114eb06b15fd3939bb1aa6c75412dbd737fe302b52c5c24334f9cb01cee8edc1d1067d98ba80dddf971f1d0e94b387de51423fc6cf3c5d8b7ef27a + languageName: node + linkType: hard + +"chevrotain@npm:~11.1.1": + version: 11.1.2 + resolution: "chevrotain@npm:11.1.2" + dependencies: + "@chevrotain/cst-dts-gen": "npm:11.1.2" + "@chevrotain/gast": "npm:11.1.2" + "@chevrotain/regexp-to-ast": "npm:11.1.2" + "@chevrotain/types": "npm:11.1.2" + "@chevrotain/utils": "npm:11.1.2" + lodash-es: "npm:4.17.23" + checksum: 10c0/7f0b5780035c582d4c620c81e1fbb58c9f41a69f1c7efdae96819c7bc0928ddb4f046bb8239e71539f383b3b8ce460bd11f44b5fb5107e1d45a0cc91bd6a4198 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.3": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"ci-info@npm:^3.2.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10c0/6f0109e36e111684291d46123d491bc4e7b7a1934c3a20dea28cba89f1d4a03acd892f5f6a81ed3855c38647e285a150e3c9ba062e38943bef57fee6c1554c3a + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.0.0": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + +"clamp@npm:^1.0.1": + version: 1.0.1 + resolution: "clamp@npm:1.0.1" + checksum: 10c0/8f95ccbc5d646a98c1d690bce820f3d060a5267242083e8994f70dc504ce441dbc7b8ef13b93819129fc166e7a5b6abd320f109bdba0f49b8dcc794710987c97 + languageName: node + linkType: hard + +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 + languageName: node + linkType: hard + +"clipboardy@npm:3.0.0": + version: 3.0.0 + resolution: "clipboardy@npm:3.0.0" + dependencies: + arch: "npm:^2.2.0" + execa: "npm:^5.1.1" + is-wsl: "npm:^2.2.0" + checksum: 10c0/299d66e13fcaccf656306e76d629ce6927eaba8ba58ae5328e3379ae627e469e29df8ef87408cdb234e2ad0e25f0024dd203393f7e59c67ae79772579c4de052 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10c0/4bda0f09c340cbb6dfdc1ed508b3ca080f12992c18d68c6be4d9cf51756033d5266e61ec57529e610dacbf4da1c634423b0c1b11037709cc6b09045cbd815df5 + languageName: node + linkType: hard + +"clsx@npm:^2.0.0, clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 10c0/c0e85ea0ca8bf0a50cbdca82efc5af0301240ca88ebe3644a6ffb8ffe911f34d40f8fbcf8f1d52c5ddd66706abd4d3bfcd64259f1e8e2371d4f47573b0dc8c28 + languageName: node + linkType: hard + +"codepage@npm:~1.15.0": + version: 1.15.0 + resolution: "codepage@npm:1.15.0" + checksum: 10c0/2455b482302cb784b46dea60a8ee83f0c23e794bdd979556bdb107abe681bba722af62a37f5c955ff4efd68fdb9688c3986e719b4fd536c0e06bb25bc82abea3 + languageName: node + linkType: hard + +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: 10c0/ed7008e2e8b6852c5483b444a3ae6e976e088d4335a85aa0a9db2861c5f1d31bd2d7ff97a60469b3388deeba661a619753afbe201279fb159b4b9548ab8269a1 + languageName: node + linkType: hard + +"color-alpha@npm:1.0.4": + version: 1.0.4 + resolution: "color-alpha@npm:1.0.4" + dependencies: + color-parse: "npm:^1.3.8" + checksum: 10c0/dd78453f9c9fc20e9303d085357594abe26ec5ea644d87ef21d6bd61e1eaf02e324d43efaa00dd68af2b7e551f4bc53c11dafd9556835c3707a7b29623053842 + languageName: node + linkType: hard + +"color-alpha@npm:^1.0.4": + version: 1.1.3 + resolution: "color-alpha@npm:1.1.3" + dependencies: + color-parse: "npm:^1.4.1" + checksum: 10c0/3481da412db67716860e86af89f75b3db550cf38879086208314863f4bd2b6004c42d6d8532970d9482bd75c47f60eab6a5002c4b3740d0e0c2f81bfb1caad15 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-id@npm:^1.1.0": + version: 1.1.0 + resolution: "color-id@npm:1.1.0" + dependencies: + clamp: "npm:^1.0.1" + checksum: 10c0/620d9184529c917e994a2b172ca2a73af33df0608596ed07ea42fed22fe308c164a08903e1dce88185e5b39ee9c7a5580cbf87a52c1fd8455c3fa8d359b6263d + languageName: node + linkType: hard + +"color-name@npm:^1.0.0, color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"color-name@npm:^2.0.0": + version: 2.1.0 + resolution: "color-name@npm:2.1.0" + checksum: 10c0/9c953caba99557fce472232ded438c56b902c569cb15d66fcfbdf6374206126eef52ab66459f3984d4074b4aa8ab95e6f4b31a8e4f228dea57d0afecf94281fa + languageName: node + linkType: hard + +"color-normalize@npm:1.5.0": + version: 1.5.0 + resolution: "color-normalize@npm:1.5.0" + dependencies: + clamp: "npm:^1.0.1" + color-rgba: "npm:^2.1.1" + dtype: "npm:^2.0.0" + checksum: 10c0/86131bb0c3c4cc16f63423c08d523915825b30c08b3b1e36f9cac52e3710209ef47f623b2f0943b6efc4f6d71f098922e6795e2546d8885398643ff1b96ee623 + languageName: node + linkType: hard + +"color-normalize@npm:^1.5.0": + version: 1.5.2 + resolution: "color-normalize@npm:1.5.2" + dependencies: + color-rgba: "npm:^2.2.0" + dtype: "npm:^2.0.0" + checksum: 10c0/d9d536b9cb9b14a5ee6dc0555f12e846ef2fdb478fddd245478ad414b6d60f0da42c9b76bf99cc23a42e30e06d5e2ff3a69ddb8e4d767f3f3ac14669ecebc5d0 + languageName: node + linkType: hard + +"color-parse@npm:2.0.0": + version: 2.0.0 + resolution: "color-parse@npm:2.0.0" + dependencies: + color-name: "npm:^1.0.0" + checksum: 10c0/f489deaea5bb323e05f41436d747e22bbb45928420fce8a989c209a4baa71c7ee90f8b43298d9565844af1d02580743d5d87b506519f755b4b218df6b72eb4a0 + languageName: node + linkType: hard + +"color-parse@npm:^1.3.8, color-parse@npm:^1.4.1, color-parse@npm:^1.4.2": + version: 1.4.3 + resolution: "color-parse@npm:1.4.3" + dependencies: + color-name: "npm:^1.0.0" + checksum: 10c0/3473e7fb53d643dffc508c4f3161890f405ece9d78cc84277576212346b17ad037d1a0c6beb49e9b55bc11435a8c85b7ff9f94a6b898eb82647d4a46615d23da + languageName: node + linkType: hard + +"color-parse@npm:^2.0.0": + version: 2.0.2 + resolution: "color-parse@npm:2.0.2" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10c0/5761baa81e3c8acf3625f4a6ca77b72e77ee9429c0b9757baab18ea1af0861919aee47e337675c47ee5bf3bc2b10cd0690e226322058e94457f2b501e1ef132b + languageName: node + linkType: hard + +"color-rgba@npm:3.0.0": + version: 3.0.0 + resolution: "color-rgba@npm:3.0.0" + dependencies: + color-parse: "npm:^2.0.0" + color-space: "npm:^2.0.0" + checksum: 10c0/8f3920cd1dcafde31c9240e54c2aba3c4fc43e350dc15cc00de3ea988800e1a533f85b38aa67867acfd51634a92bfd5e8aa9f06f1b42965c8fa538b5e16d9667 + languageName: node + linkType: hard + +"color-rgba@npm:^2.1.1, color-rgba@npm:^2.2.0": + version: 2.4.0 + resolution: "color-rgba@npm:2.4.0" + dependencies: + color-parse: "npm:^1.4.2" + color-space: "npm:^2.0.0" + checksum: 10c0/b2d952a464a51ff446927b60528c94caee8987bcf1609dce7a18589a2d2febeab79d8c66c9eb1c4d9ef69ae4b5dfebc9f8fc858ad45d6cae7d2b013c562893ee + languageName: node + linkType: hard + +"color-space@npm:^2.0.0": + version: 2.3.1 + resolution: "color-space@npm:2.3.1" + checksum: 10c0/39fff47471a4f27f0ec7e71ee52088b1a4a7171a42c7d2532c9abcf0c2ab18309d4da3317d8a2158d786e3c46729a6051a6df3fa7a015e211d8987f909b57643 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"comma-separated-tokens@npm:^2.0.0": + version: 2.0.3 + resolution: "comma-separated-tokens@npm:2.0.3" + checksum: 10c0/91f90f1aae320f1755d6957ef0b864fe4f54737f3313bd95e0802686ee2ca38bff1dd381964d00ae5db42912dd1f4ae5c2709644e82706ffc6f6842a813cdd67 + languageName: node + linkType: hard + +"commander@npm:2, commander@npm:^2.15.1": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288 + languageName: node + linkType: hard + +"commander@npm:7": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 10c0/8d690ff13b0356df7e0ebbe6c59b4712f754f4b724d4f473d3cc5b3fdcf978e3a5dc3078717858a2ceb50b0f84d0660a7f22a96cdc50fb877d0c9bb31593d23a + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10c0/8b043bb8322ea1c39664a1598a95e0495bfe4ca2fad0d84a92d7d1d8d213e2a155b441d2470c8e08de7c4a28cf2bc6e169211c49e1b21d9f7edc6ae4d9356060 + languageName: node + linkType: hard + +"compressible@npm:~2.0.18": + version: 2.0.18 + resolution: "compressible@npm:2.0.18" + dependencies: + mime-db: "npm:>= 1.43.0 < 2" + checksum: 10c0/8a03712bc9f5b9fe530cc5a79e164e665550d5171a64575d7dcf3e0395d7b4afa2d79ab176c61b5b596e28228b350dd07c1a2a6ead12fd81d1b6cd632af2fef7 + languageName: node + linkType: hard + +"compression@npm:1.8.1": + version: 1.8.1 + resolution: "compression@npm:1.8.1" + dependencies: + bytes: "npm:3.1.2" + compressible: "npm:~2.0.18" + debug: "npm:2.6.9" + negotiator: "npm:~0.6.4" + on-headers: "npm:~1.1.0" + safe-buffer: "npm:5.2.1" + vary: "npm:~1.1.2" + checksum: 10c0/85114b0b91c16594dc8c671cd9b05ef5e465066a60e5a4ed8b4551661303559a896ed17bb72c4234c04064e078f6ca86a34b8690349499a43f6fc4b844475da4 + languageName: node + linkType: hard + +"compute-scroll-into-view@npm:^3.1.1": + version: 3.1.1 + resolution: "compute-scroll-into-view@npm:3.1.1" + checksum: 10c0/59761ed62304a9599b52ad75d0d6fbf0669ee2ab7dd472fdb0ad9da36628414c014dea7b5810046560180ad30ffec52a953d19297f66a1d4f3aa0999b9d2521d + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"concat-stream@npm:^1.5.2": + version: 1.6.2 + resolution: "concat-stream@npm:1.6.2" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^2.2.2" + typedarray: "npm:^0.0.6" + checksum: 10c0/2e9864e18282946dabbccb212c5c7cec0702745e3671679eb8291812ca7fd12023f7d8cb36493942a62f770ac96a7f90009dc5c82ad69893438371720fa92617 + languageName: node + linkType: hard + +"confbox@npm:^0.1.8": + version: 0.1.8 + resolution: "confbox@npm:0.1.8" + checksum: 10c0/fc2c68d97cb54d885b10b63e45bd8da83a8a71459d3ecf1825143dd4c7f9f1b696b3283e07d9d12a144c1301c2ebc7842380bdf0014e55acc4ae1c9550102418 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.2": + version: 0.5.2 + resolution: "content-disposition@npm:0.5.2" + checksum: 10c0/49eebaa0da1f9609b192e99d7fec31d1178cb57baa9d01f5b63b29787ac31e9d18b5a1033e854c68c9b6cce790e700a6f7fa60e43f95e2e416404e114a8f2f49 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie@npm:^1.0.1": + version: 1.0.2 + resolution: "cookie@npm:1.0.2" + checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cose-base@npm:^1.0.0": + version: 1.0.3 + resolution: "cose-base@npm:1.0.3" + dependencies: + layout-base: "npm:^1.0.0" + checksum: 10c0/a6e400b1d101393d6af0967c1353355777c1106c40417c5acaef6ca8bdda41e2fc9398f466d6c85be30290943ad631f2590569f67b3fd5368a0d8318946bd24f + languageName: node + linkType: hard + +"cose-base@npm:^2.2.0": + version: 2.2.0 + resolution: "cose-base@npm:2.2.0" + dependencies: + layout-base: "npm:^2.0.0" + checksum: 10c0/14b9f8100ac322a00777ffb1daeb3321af368bbc9cabe3103943361273baee2003202ffe38e4ab770960b600214224e9c196195a78d589521540aa694df7cdec + languageName: node + linkType: hard + +"cosmiconfig@npm:^8.1.3": + version: 8.3.6 + resolution: "cosmiconfig@npm:8.3.6" + dependencies: + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + path-type: "npm:^4.0.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10c0/0382a9ed13208f8bfc22ca2f62b364855207dffdb73dc26e150ade78c3093f1cf56172df2dd460c8caf2afa91c0ed4ec8a88c62f8f9cd1cf423d26506aa8797a + languageName: node + linkType: hard + +"country-regex@npm:^1.1.0": + version: 1.1.0 + resolution: "country-regex@npm:1.1.0" + checksum: 10c0/3e95be9c3065dbf7dfafae8bb951e36b2805b8dd7dbba5688c74cedaf7c86effd74b7a7b43116bf57709560e7ace7e5001d34f62bf25802aa1a90052dba1a667 + languageName: node + linkType: hard + +"crc-32@npm:~1.2.0, crc-32@npm:~1.2.1": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: 10c0/11dcf4a2e77ee793835d49f2c028838eae58b44f50d1ff08394a610bfd817523f105d6ae4d9b5bef0aad45510f633eb23c903e9902e4409bed1ce70cb82b9bf0 + languageName: node + linkType: hard + +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10c0/e7e54c280692470d3398f62a6238fd396327e01c6a0757002833f06d00afc62dd7bfe04ff2b9cd145264460e6b4d1eb8386f2925b7e567f97939843b7b0e812f + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10c0/157cbc59b2430ae9a90034a5f3a1b398b6738bf510f713edc4d4e45e169bc514d3d99dd34d8d01ca7ae7830b5b8b537e46ae8f3c8f932371b0875c0151d7ec91 + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.0": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10c0/bf61fb890e8635102ea9bce050515cf915ff6a50ccaa0b37a17dc82fded0fb3ed7af5478b9367b86baee19127ad86af4be51d209f64fd6638c0862dca185fe1d + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"css-color-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-color-keywords@npm:1.0.0" + checksum: 10c0/af205a86c68e0051846ed91eb3e30b4517e1904aac040013ff1d742019b3f9369ba5658ba40901dbbc121186fc4bf0e75a814321cc3e3182fbb2feb81c6d9cb7 + languageName: node + linkType: hard + +"css-font-size-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-font-size-keywords@npm:1.0.0" + checksum: 10c0/afc3e296a20e533da5fe84e1436914b5ec789d2db851b856688a1aa80f88c2ee408580a8a0a1e0581cc0952b65bfb37a769c841df185c7b1d95198c563996465 + languageName: node + linkType: hard + +"css-font-stretch-keywords@npm:^1.0.1": + version: 1.0.1 + resolution: "css-font-stretch-keywords@npm:1.0.1" + checksum: 10c0/9f5c405c99962f2bf77ed79f9f137ac7eecbc2e0f64cfe448277af9924ec2717214369d0e9a02b4520e3e0812b6a5289fad36911678a13f5505e972889cb27a9 + languageName: node + linkType: hard + +"css-font-style-keywords@npm:^1.0.1": + version: 1.0.1 + resolution: "css-font-style-keywords@npm:1.0.1" + checksum: 10c0/431bb7ee0346302abc9c94a74df0c20b8f5173ef8b29284c571bbba4eec4e22a461dcb6a7d0ae764f9b02f9a7a185612193906122c744af96eb6e4be72e646d2 + languageName: node + linkType: hard + +"css-font-weight-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-font-weight-keywords@npm:1.0.0" + checksum: 10c0/99fbf6ca55ca86cbb083786f6ca83d47c0284eab1a96929b71401a50969e18a901474ffe8b1a895226f9bbde461c77d88ca50fcc871ce8a8926a2d2ba65bb847 + languageName: node + linkType: hard + +"css-font@npm:^1.0.0, css-font@npm:^1.2.0": + version: 1.2.0 + resolution: "css-font@npm:1.2.0" + dependencies: + css-font-size-keywords: "npm:^1.0.0" + css-font-stretch-keywords: "npm:^1.0.1" + css-font-style-keywords: "npm:^1.0.1" + css-font-weight-keywords: "npm:^1.0.0" + css-global-keywords: "npm:^1.0.1" + css-system-font-keywords: "npm:^1.0.0" + pick-by-alias: "npm:^1.2.0" + string-split-by: "npm:^1.0.0" + unquote: "npm:^1.1.0" + checksum: 10c0/6a68aeb6529e179ff3d5d4de52f96f89de507d06b69a0d3899c0d06448530a6e755342f390a42bf9c8672411694618a9a6e204dc808918cd98d5c08cedc5455e + languageName: node + linkType: hard + +"css-global-keywords@npm:^1.0.1": + version: 1.0.1 + resolution: "css-global-keywords@npm:1.0.1" + checksum: 10c0/c49de806fa532fda2c2ba5b27b3443a2ad3d7b05a0a80d9eeae81231a67b7fa09b772dc87226d82a62b8a429141001a62f9eb3b78a4ad8ee85551beb65215a48 + languageName: node + linkType: hard + +"css-system-font-keywords@npm:^1.0.0": + version: 1.0.0 + resolution: "css-system-font-keywords@npm:1.0.0" + checksum: 10c0/8eb3c1d70f70f4727d273cfdc37c53cb0e312286c070f9ec1cf24b7c2602cf8b128f2a127004f1f9feca45848442ad99c22875a440457a238fcd3b9307c8f0ce + languageName: node + linkType: hard + +"css-to-react-native@npm:3.2.0": + version: 3.2.0 + resolution: "css-to-react-native@npm:3.2.0" + dependencies: + camelize: "npm:^1.0.0" + css-color-keywords: "npm:^1.0.0" + postcss-value-parser: "npm:^4.0.2" + checksum: 10c0/fde850a511d5d3d7c55a1e9b8ed26b69a8ad4868b3487e36ebfbfc0b96fc34bc977d9cd1d61a289d0c74d3f9a662d8cee297da53d4433bf2e27d6acdff8e1003 + languageName: node + linkType: hard + +"csscolorparser@npm:~1.0.3": + version: 1.0.3 + resolution: "csscolorparser@npm:1.0.3" + checksum: 10c0/57b30e1dd3e639fb74d63d3ee5a078ae6d0aaba26bc731fdec1a0f50fd77c2531a1fca2bbe07c18e0569255f92064cda0747e0115955fdb8c037332798ca634f + languageName: node + linkType: hard + +"cssom@npm:^0.5.0": + version: 0.5.0 + resolution: "cssom@npm:0.5.0" + checksum: 10c0/8c4121c243baf0678c65dcac29b201ff0067dfecf978de9d5c83b2ff127a8fdefd2bfd54577f5ad8c80ed7d2c8b489ae01c82023545d010c4ecb87683fb403dd + languageName: node + linkType: hard + +"cssom@npm:~0.3.6": + version: 0.3.8 + resolution: "cssom@npm:0.3.8" + checksum: 10c0/d74017b209440822f9e24d8782d6d2e808a8fdd58fa626a783337222fe1c87a518ba944d4c88499031b4786e68772c99dfae616638d71906fe9f203aeaf14411 + languageName: node + linkType: hard + +"cssstyle@npm:^2.3.0": + version: 2.3.0 + resolution: "cssstyle@npm:2.3.0" + dependencies: + cssom: "npm:~0.3.6" + checksum: 10c0/863400da2a458f73272b9a55ba7ff05de40d850f22eb4f37311abebd7eff801cf1cd2fb04c4c92b8c3daed83fe766e52e4112afb7bc88d86c63a9c2256a7d178 + languageName: node + linkType: hard + +"csstype@npm:3.2.3, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"csstype@npm:^3.0.2": + version: 3.1.3 + resolution: "csstype@npm:3.1.3" + checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 + languageName: node + linkType: hard + +"cytoscape-cose-bilkent@npm:^4.1.0": + version: 4.1.0 + resolution: "cytoscape-cose-bilkent@npm:4.1.0" + dependencies: + cose-base: "npm:^1.0.0" + peerDependencies: + cytoscape: ^3.2.0 + checksum: 10c0/5e2480ddba9da1a68e700ed2c674cbfd51e9efdbd55788f1971a68de4eb30708e3b3a5e808bf5628f7a258680406bbe6586d87a9133e02a9bdc1ab1a92f512f2 + languageName: node + linkType: hard + +"cytoscape-fcose@npm:^2.2.0": + version: 2.2.0 + resolution: "cytoscape-fcose@npm:2.2.0" + dependencies: + cose-base: "npm:^2.2.0" + peerDependencies: + cytoscape: ^3.2.0 + checksum: 10c0/ce472c9f85b9057e75c5685396f8e1f2468895e71b184913e05ad56dcf3092618fe59a1054f29cb0995051ba8ebe566ad0dd49a58d62845145624bd60cd44917 + languageName: node + linkType: hard + +"cytoscape@npm:^3.33.1": + version: 3.33.2 + resolution: "cytoscape@npm:3.33.2" + checksum: 10c0/2f3c480df7792419e526348cec4e97237399221080016cd96c17f0686be5245eba4651517cbf5639a6b8b186178a0e438b8fbd7f306384acf3c74db4f4c3ccb3 + languageName: node + linkType: hard + +"d3-array@npm:1 - 2": + version: 2.12.1 + resolution: "d3-array@npm:2.12.1" + dependencies: + internmap: "npm:^1.0.0" + checksum: 10c0/7eca10427a9f113a4ca6a0f7301127cab26043fd5e362631ef5a0edd1c4b2dd70c56ed317566700c31e4a6d88b55f3951aaba192291817f243b730cb2352882e + languageName: node + linkType: hard + +"d3-array@npm:1, d3-array@npm:^1.2.1": + version: 1.2.4 + resolution: "d3-array@npm:1.2.4" + checksum: 10c0/7ac0ae096838e75d06350381442d84b327e3215d470f26c297851675bd25c47a633d35b04bfaa0397c529f42428d19f3f80bead24e1e866832e064cc6af24f3a + languageName: node + linkType: hard + +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:2.5.0 - 3, d3-array@npm:3, d3-array@npm:^3.2.0": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: "npm:1 - 2" + checksum: 10c0/08b95e91130f98c1375db0e0af718f4371ccacef7d5d257727fe74f79a24383e79aba280b9ffae655483ffbbad4fd1dec4ade0119d88c4749f388641c8bf8c50 + languageName: node + linkType: hard + +"d3-axis@npm:3": + version: 3.0.0 + resolution: "d3-axis@npm:3.0.0" + checksum: 10c0/a271e70ba1966daa5aaf6a7f959ceca3e12997b43297e757c7b945db2e1ead3c6ee226f2abcfa22abbd4e2e28bd2b71a0911794c4e5b911bbba271328a582c78 + languageName: node + linkType: hard + +"d3-brush@npm:3": + version: 3.0.0 + resolution: "d3-brush@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:3" + d3-transition: "npm:3" + checksum: 10c0/07baf00334c576da2f68a91fc0da5732c3a5fa19bd3d7aed7fd24d1d674a773f71a93e9687c154176f7246946194d77c48c2d8fed757f5dcb1a4740067ec50a8 + languageName: node + linkType: hard + +"d3-chord@npm:3": + version: 3.0.1 + resolution: "d3-chord@npm:3.0.1" + dependencies: + d3-path: "npm:1 - 3" + checksum: 10c0/baa6013914af3f4fe1521f0d16de31a38eb8a71d08ff1dec4741f6f45a828661e5cd3935e39bd14e3032bdc78206c283ca37411da21d46ec3cfc520be6e7a7ce + languageName: node + linkType: hard + +"d3-collection@npm:1, d3-collection@npm:^1.0.4": + version: 1.0.7 + resolution: "d3-collection@npm:1.0.7" + checksum: 10c0/7a3c7f733ce4a1a02f46a96c7dd02f8e46a2fa83fc4195682fd33624d6a56fbda6388c86ff5d30799cc768cb63bffcdb216b45c51a8d6e0b23117db9c18bedb3 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3, d3-color@npm:3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 10c0/a4e20e1115fa696fce041fbe13fbc80dc4c19150fa72027a7c128ade980bc0eeeba4bcf28c9e21f0bce0e0dbfe7ca5869ef67746541dcfda053e4802ad19783c + languageName: node + linkType: hard + +"d3-contour@npm:4": + version: 4.0.2 + resolution: "d3-contour@npm:4.0.2" + dependencies: + d3-array: "npm:^3.2.0" + checksum: 10c0/98bc5fbed6009e08707434a952076f39f1cd6ed8b9288253cc3e6a3286e4e80c63c62d84954b20e64bf6e4ededcc69add54d3db25e990784a59c04edd3449032 + languageName: node + linkType: hard + +"d3-delaunay@npm:6": + version: 6.0.4 + resolution: "d3-delaunay@npm:6.0.4" + dependencies: + delaunator: "npm:5" + checksum: 10c0/57c3aecd2525664b07c4c292aa11cf49b2752c0cf3f5257f752999399fe3c592de2d418644d79df1f255471eec8057a9cc0c3062ed7128cb3348c45f69597754 + languageName: node + linkType: hard + +"d3-dispatch@npm:1": + version: 1.0.6 + resolution: "d3-dispatch@npm:1.0.6" + checksum: 10c0/6302554a019e2d75d4e3dc7e8757a00b4b12ac2a2952bccc66e4478ccd170f425e2b6a9443118d5feadcd2439f33582b63c7925e832104ff1978cadea2a30dc2 + languageName: node + linkType: hard + +"d3-dispatch@npm:1 - 3, d3-dispatch@npm:3": + version: 3.0.1 + resolution: "d3-dispatch@npm:3.0.1" + checksum: 10c0/6eca77008ce2dc33380e45d4410c67d150941df7ab45b91d116dbe6d0a3092c0f6ac184dd4602c796dc9e790222bad3ff7142025f5fd22694efe088d1d941753 + languageName: node + linkType: hard + +"d3-drag@npm:2 - 3, d3-drag@npm:3": + version: 3.0.0 + resolution: "d3-drag@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-selection: "npm:3" + checksum: 10c0/d2556e8dc720741a443b595a30af403dd60642dfd938d44d6e9bfc4c71a962142f9a028c56b61f8b4790b65a34acad177d1263d66f103c3c527767b0926ef5aa + languageName: node + linkType: hard + +"d3-dsv@npm:1 - 3, d3-dsv@npm:3": + version: 3.0.1 + resolution: "d3-dsv@npm:3.0.1" + dependencies: + commander: "npm:7" + iconv-lite: "npm:0.6" + rw: "npm:1" + bin: + csv2json: bin/dsv2json.js + csv2tsv: bin/dsv2dsv.js + dsv2dsv: bin/dsv2dsv.js + dsv2json: bin/dsv2json.js + json2csv: bin/json2dsv.js + json2dsv: bin/json2dsv.js + json2tsv: bin/json2dsv.js + tsv2csv: bin/dsv2dsv.js + tsv2json: bin/dsv2json.js + checksum: 10c0/10e6af9e331950ed258f34ab49ac1b7060128ef81dcf32afc790bd1f7e8c3cc2aac7f5f875250a83f21f39bb5925fbd0872bb209f8aca32b3b77d32bab8a65ab + languageName: node + linkType: hard + +"d3-ease@npm:1 - 3, d3-ease@npm:3": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 10c0/fec8ef826c0cc35cda3092c6841e07672868b1839fcaf556e19266a3a37e6bc7977d8298c0fcb9885e7799bfdcef7db1baaba9cd4dcf4bc5e952cf78574a88b0 + languageName: node + linkType: hard + +"d3-fetch@npm:3": + version: 3.0.1 + resolution: "d3-fetch@npm:3.0.1" + dependencies: + d3-dsv: "npm:1 - 3" + checksum: 10c0/4f467a79bf290395ac0cbb5f7562483f6a18668adc4c8eb84c9d3eff048b6f6d3b6f55079ba1ebf1908dabe000c941d46be447f8d78453b2dad5fb59fb6aa93b + languageName: node + linkType: hard + +"d3-force@npm:3": + version: 3.0.0 + resolution: "d3-force@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-quadtree: "npm:1 - 3" + d3-timer: "npm:1 - 3" + checksum: 10c0/220a16a1a1ac62ba56df61028896e4b52be89c81040d20229c876efc8852191482c233f8a52bb5a4e0875c321b8e5cb6413ef3dfa4d8fe79eeb7d52c587f52cf + languageName: node + linkType: hard + +"d3-force@npm:^1.2.1": + version: 1.2.1 + resolution: "d3-force@npm:1.2.1" + dependencies: + d3-collection: "npm:1" + d3-dispatch: "npm:1" + d3-quadtree: "npm:1" + d3-timer: "npm:1" + checksum: 10c0/af699875547e3f5d8fc66ea5dd2a5479e06560a7583c37f3262e81474b8f38e44593124925f75796ac686d9db83831d52cbabbb650cc88c7b1235f51cfebe808 + languageName: node + linkType: hard + +"d3-format@npm:1 - 3, d3-format@npm:3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: 10c0/049f5c0871ebce9859fc5e2f07f336b3c5bfff52a2540e0bac7e703fce567cd9346f4ad1079dd18d6f1e0eaa0599941c1810898926f10ac21a31fd0a34b4aa75 + languageName: node + linkType: hard + +"d3-format@npm:^1.4.5": + version: 1.4.5 + resolution: "d3-format@npm:1.4.5" + checksum: 10c0/40800a2fb2182d2d711cea3acc2b8b2b3afdb6f644c51de77feb9b08a6150b14c753933d2fd4ad2f6f45130757b738673372c45b4b820466c560f3b1ec0b3ce8 + languageName: node + linkType: hard + +"d3-geo-projection@npm:^2.9.0": + version: 2.9.0 + resolution: "d3-geo-projection@npm:2.9.0" + dependencies: + commander: "npm:2" + d3-array: "npm:1" + d3-geo: "npm:^1.12.0" + resolve: "npm:^1.1.10" + bin: + geo2svg: bin/geo2svg + geograticule: bin/geograticule + geoproject: bin/geoproject + geoquantize: bin/geoquantize + geostitch: bin/geostitch + checksum: 10c0/eecbb87d1ffaea0a339035f7cc37d366fe877bb1c212f2034d8e44b14669815926f29bf8ca595beee7a620c82f10dc8cd67f461a94b79e0f9415a0aae7918a5d + languageName: node + linkType: hard + +"d3-geo@npm:3": + version: 3.1.1 + resolution: "d3-geo@npm:3.1.1" + dependencies: + d3-array: "npm:2.5.0 - 3" + checksum: 10c0/d32270dd2dc8ac3ea63e8805d63239c4c8ec6c0d339d73b5e5a30a87f8f54db22a78fb434369799465eae169503b25f9a107c642c8a16c32a3285bc0e6d8e8c1 + languageName: node + linkType: hard + +"d3-geo@npm:^1.12.0, d3-geo@npm:^1.12.1": + version: 1.12.1 + resolution: "d3-geo@npm:1.12.1" + dependencies: + d3-array: "npm:1" + checksum: 10c0/ec46ff8fdd824df2fbcb9c6e7a6e8c778ecaa196ba1a3265fa6b58d32e526d258dda7d6033698f9625189d364d70f5a9b9a7f6f54fdefe8ef677c28e9765b602 + languageName: node + linkType: hard + +"d3-hierarchy@npm:3": + version: 3.1.2 + resolution: "d3-hierarchy@npm:3.1.2" + checksum: 10c0/6dcdb480539644aa7fc0d72dfc7b03f99dfbcdf02714044e8c708577e0d5981deb9d3e99bbbb2d26422b55bcc342ac89a0fa2ea6c9d7302e2fc0951dd96f89cf + languageName: node + linkType: hard + +"d3-hierarchy@npm:^1.1.9": + version: 1.1.9 + resolution: "d3-hierarchy@npm:1.1.9" + checksum: 10c0/63b0ae0953bda076866b8705f8ea6fa1f67ded7ee99d98b20ef4364ce21868c292c9b45e887fde0f0dba1d0202466b2a87e7d5a6cc6388e759aadc5f055142e0 + languageName: node + linkType: hard + +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3, d3-interpolate@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 10c0/19f4b4daa8d733906671afff7767c19488f51a43d251f8b7f484d5d3cfc36c663f0a66c38fe91eee30f40327443d799be17169f55a293a3ba949e84e57a33e6a + languageName: node + linkType: hard + +"d3-path@npm:1": + version: 1.0.9 + resolution: "d3-path@npm:1.0.9" + checksum: 10c0/e35e84df5abc18091f585725b8235e1fa97efc287571585427d3a3597301e6c506dea56b11dfb3c06ca5858b3eb7f02c1bf4f6a716aa9eade01c41b92d497eb5 + languageName: node + linkType: hard + +"d3-path@npm:1 - 3, d3-path@npm:3, d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 10c0/dc1d58ec87fa8319bd240cf7689995111a124b141428354e9637aa83059eb12e681f77187e0ada5dedfce346f7e3d1f903467ceb41b379bfd01cd8e31721f5da + languageName: node + linkType: hard + +"d3-polygon@npm:3": + version: 3.0.1 + resolution: "d3-polygon@npm:3.0.1" + checksum: 10c0/e236aa7f33efa9a4072907af7dc119f85b150a0716759d4fe5f12f62573018264a6cbde8617fbfa6944a7ae48c1c0c8d3f39ae72e11f66dd471e9b5e668385df + languageName: node + linkType: hard + +"d3-quadtree@npm:1": + version: 1.0.7 + resolution: "d3-quadtree@npm:1.0.7" + checksum: 10c0/90571c7bfc7ee2db4bb3f80b1b06f3c653858c365644c31a0ecf0adec525c0d8a70c4eaa88bc8d364b1fa3c919ee55cc55cf82396f4729d2a4baec2b95832492 + languageName: node + linkType: hard + +"d3-quadtree@npm:1 - 3, d3-quadtree@npm:3": + version: 3.0.1 + resolution: "d3-quadtree@npm:3.0.1" + checksum: 10c0/18302d2548bfecaef788152397edec95a76400fd97d9d7f42a089ceb68d910f685c96579d74e3712d57477ed042b056881b47cd836a521de683c66f47ce89090 + languageName: node + linkType: hard + +"d3-random@npm:3": + version: 3.0.1 + resolution: "d3-random@npm:3.0.1" + checksum: 10c0/987a1a1bcbf26e6cf01fd89d5a265b463b2cea93560fc17d9b1c45e8ed6ff2db5924601bcceb808de24c94133f000039eb7fa1c469a7a844ccbf1170cbb25b41 + languageName: node + linkType: hard + +"d3-sankey@npm:^0.12.3": + version: 0.12.3 + resolution: "d3-sankey@npm:0.12.3" + dependencies: + d3-array: "npm:1 - 2" + d3-shape: "npm:^1.2.0" + checksum: 10c0/261debb01a13269f6fc53b9ebaef174a015d5ad646242c23995bf514498829ab8b8f920a7873724a7494288b46bea3ce7ebc5a920b745bc8ae4caa5885cf5204 + languageName: node + linkType: hard + +"d3-scale-chromatic@npm:3": + version: 3.1.0 + resolution: "d3-scale-chromatic@npm:3.1.0" + dependencies: + d3-color: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + checksum: 10c0/9a3f4671ab0b971f4a411b42180d7cf92bfe8e8584e637ce7e698d705e18d6d38efbd20ec64f60cc0dfe966c20d40fc172565bc28aaa2990c0a006360eed91af + languageName: node + linkType: hard + +"d3-scale@npm:4": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: "npm:2.10.0 - 3" + d3-format: "npm:1 - 3" + d3-interpolate: "npm:1.2.0 - 3" + d3-time: "npm:2.1.1 - 3" + d3-time-format: "npm:2 - 4" + checksum: 10c0/65d9ad8c2641aec30ed5673a7410feb187a224d6ca8d1a520d68a7d6eac9d04caedbff4713d1e8545be33eb7fec5739983a7ab1d22d4e5ad35368c6729d362f1 + languageName: node + linkType: hard + +"d3-selection@npm:2 - 3, d3-selection@npm:3, d3-selection@npm:^3.0.0": + version: 3.0.0 + resolution: "d3-selection@npm:3.0.0" + checksum: 10c0/e59096bbe8f0cb0daa1001d9bdd6dbc93a688019abc97d1d8b37f85cd3c286a6875b22adea0931b0c88410d025563e1643019161a883c516acf50c190a11b56b + languageName: node + linkType: hard + +"d3-shape@npm:3": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: "npm:^3.1.0" + checksum: 10c0/f1c9d1f09926daaf6f6193ae3b4c4b5521e81da7d8902d24b38694517c7f527ce3c9a77a9d3a5722ad1e3ff355860b014557b450023d66a944eabf8cfde37132 + languageName: node + linkType: hard + +"d3-shape@npm:^1.2.0": + version: 1.3.7 + resolution: "d3-shape@npm:1.3.7" + dependencies: + d3-path: "npm:1" + checksum: 10c0/548057ce59959815decb449f15632b08e2a1bdce208f9a37b5f98ec7629dda986c2356bc7582308405ce68aedae7d47b324df41507404df42afaf352907577ae + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4, d3-time-format@npm:4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: "npm:1 - 3" + checksum: 10c0/735e00fb25a7fd5d418fac350018713ae394eefddb0d745fab12bbff0517f9cdb5f807c7bbe87bb6eeb06249662f8ea84fec075f7d0cd68609735b2ceb29d206 + languageName: node + linkType: hard + +"d3-time-format@npm:^2.2.3": + version: 2.3.0 + resolution: "d3-time-format@npm:2.3.0" + dependencies: + d3-time: "npm:1" + checksum: 10c0/4ca7b5b4ac8fcf4b7930a5dc4ee5f34b188ebd0a36029df2acee0f68b5028451c045c63a12e3970da8e9840548588bde3d366ef8ee7fcb107459d4a6ad11d8c5 + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: "npm:2 - 3" + checksum: 10c0/a984f77e1aaeaa182679b46fbf57eceb6ebdb5f67d7578d6f68ef933f8eeb63737c0949991618a8d29472dbf43736c7d7f17c452b2770f8c1271191cba724ca1 + languageName: node + linkType: hard + +"d3-time@npm:1, d3-time@npm:^1.1.0": + version: 1.1.0 + resolution: "d3-time@npm:1.1.0" + checksum: 10c0/69ab137adff5b22d0fa148ea514a207bd9cd7d2c042ccf34a268f2ef73720b404f0be6e7b56c95650c53caf52080b5254e2a27f0a676f41d1dd22ef8872c8335 + languageName: node + linkType: hard + +"d3-timer@npm:1": + version: 1.0.10 + resolution: "d3-timer@npm:1.0.10" + checksum: 10c0/7e77030a206861e4e626754c689795d43f036fb07a7f8ca6360eb8b7cbe6f52bf43c9c4297ae9a9a906e4de594212702f83c0cde23d4e20d8689a4211e438155 + languageName: node + linkType: hard + +"d3-timer@npm:1 - 3, d3-timer@npm:3": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 10c0/d4c63cb4bb5461d7038aac561b097cd1c5673969b27cbdd0e87fa48d9300a538b9e6f39b4a7f0e3592ef4f963d858c8a9f0e92754db73116770856f2fc04561a + languageName: node + linkType: hard + +"d3-transition@npm:2 - 3, d3-transition@npm:3, d3-transition@npm:^3.0.1": + version: 3.0.1 + resolution: "d3-transition@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + d3-dispatch: "npm:1 - 3" + d3-ease: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + d3-timer: "npm:1 - 3" + peerDependencies: + d3-selection: 2 - 3 + checksum: 10c0/4e74535dda7024aa43e141635b7522bb70cf9d3dfefed975eb643b36b864762eca67f88fafc2ca798174f83ca7c8a65e892624f824b3f65b8145c6a1a88dbbad + languageName: node + linkType: hard + +"d3-zoom@npm:3": + version: 3.0.0 + resolution: "d3-zoom@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:2 - 3" + d3-transition: "npm:2 - 3" + checksum: 10c0/ee2036479049e70d8c783d594c444fe00e398246048e3f11a59755cd0e21de62ece3126181b0d7a31bf37bcf32fd726f83ae7dea4495ff86ec7736ce5ad36fd3 + languageName: node + linkType: hard + +"d3@npm:^7.9.0": + version: 7.9.0 + resolution: "d3@npm:7.9.0" + dependencies: + d3-array: "npm:3" + d3-axis: "npm:3" + d3-brush: "npm:3" + d3-chord: "npm:3" + d3-color: "npm:3" + d3-contour: "npm:4" + d3-delaunay: "npm:6" + d3-dispatch: "npm:3" + d3-drag: "npm:3" + d3-dsv: "npm:3" + d3-ease: "npm:3" + d3-fetch: "npm:3" + d3-force: "npm:3" + d3-format: "npm:3" + d3-geo: "npm:3" + d3-hierarchy: "npm:3" + d3-interpolate: "npm:3" + d3-path: "npm:3" + d3-polygon: "npm:3" + d3-quadtree: "npm:3" + d3-random: "npm:3" + d3-scale: "npm:4" + d3-scale-chromatic: "npm:3" + d3-selection: "npm:3" + d3-shape: "npm:3" + d3-time: "npm:3" + d3-time-format: "npm:4" + d3-timer: "npm:3" + d3-transition: "npm:3" + d3-zoom: "npm:3" + checksum: 10c0/3dd9c08c73cfaa69c70c49e603c85e049c3904664d9c79a1a52a0f52795828a1ff23592dc9a7b2257e711d68a615472a13103c212032f38e016d609796e087e8 + languageName: node + linkType: hard + +"d@npm:1, d@npm:^1.0.1, d@npm:^1.0.2": + version: 1.0.2 + resolution: "d@npm:1.0.2" + dependencies: + es5-ext: "npm:^0.10.64" + type: "npm:^2.7.2" + checksum: 10c0/3e6ede10cd3b77586c47da48423b62bed161bf1a48bdbcc94d87263522e22f5dfb0e678a6dba5323fdc14c5d8612b7f7eb9e7d9e37b2e2d67a7bf9f116dabe5a + languageName: node + linkType: hard + +"dagre-d3-es@npm:7.0.14": + version: 7.0.14 + resolution: "dagre-d3-es@npm:7.0.14" + dependencies: + d3: "npm:^7.9.0" + lodash-es: "npm:^4.17.21" + checksum: 10c0/0dc91fc79300eb0a4eab5a48a76c2baf3ce439c389d19e2f015729bb57dafd75e1e9a4c2880daf016e81ee45caca7b21745c13b23b6cd2a786ce84767e88323e + languageName: node + linkType: hard + +"data-urls@npm:^3.0.2": + version: 3.0.2 + resolution: "data-urls@npm:3.0.2" + dependencies: + abab: "npm:^2.0.6" + whatwg-mimetype: "npm:^3.0.0" + whatwg-url: "npm:^11.0.0" + checksum: 10c0/051c3aaaf3e961904f136aab095fcf6dff4db23a7fc759dd8ba7b3e6ba03fc07ef608086caad8ab910d864bd3b5e57d0d2f544725653d77c96a2c971567045f4 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.19": + version: 1.11.20 + resolution: "dayjs@npm:1.11.20" + checksum: 10c0/8af525e2aa100c8db9923d706c42b2b2d30579faf89456619413a5c10916efc92c2b166e193c27c02eb3174b30aa440ee1e7b72b0a2876b3da651d204db848a0 + languageName: node + linkType: hard + +"debug@npm:2, debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 10c0/121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de + languageName: node + linkType: hard + +"debug@npm:^3.2.6": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + +"decimal.js@npm:^10.4.2": + version: 10.5.0 + resolution: "decimal.js@npm:10.5.0" + checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3 + languageName: node + linkType: hard + +"decode-named-character-reference@npm:^1.0.0": + version: 1.1.0 + resolution: "decode-named-character-reference@npm:1.1.0" + dependencies: + character-entities: "npm:^2.0.0" + checksum: 10c0/359c76305b47e67660ec096c5cd3f65972ed75b8a53a40435a7a967cfab3e9516e64b443cbe0c7edcf5ab77f65a6924f12fb1872b1e09e2f044f28f4fd10996a + languageName: node + linkType: hard + +"dedent@npm:^1.0.0": + version: 1.5.3 + resolution: "dedent@npm:1.5.3" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10c0/d94bde6e6f780be4da4fd760288fcf755ec368872f4ac5218197200d86430aeb8d90a003a840bff1c20221188e3f23adced0119cb811c6873c70d0ac66d12832 + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566 + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044 + languageName: node + linkType: hard + +"defined@npm:^1.0.0": + version: 1.0.1 + resolution: "defined@npm:1.0.1" + checksum: 10c0/357212c95fd69c3b431f4766440f1b10a8362d2663b86e3d7c139fe7fc98a1d5a4996b8b55ca62e97fb882f9887374b76944d29f9650a07993d98e7be86a804a + languageName: node + linkType: hard + +"delaunator@npm:5": + version: 5.0.1 + resolution: "delaunator@npm:5.0.1" + dependencies: + robust-predicates: "npm:^3.0.2" + checksum: 10c0/3d7ea4d964731c5849af33fec0a271bc6753487b331fd7d43ccb17d77834706e1c383e6ab8fda0032da955e7576d1083b9603cdaf9cbdfd6b3ebd1fb8bb675a5 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"dequal@npm:^2.0.0, dequal@npm:^2.0.3": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888 + languageName: node + linkType: hard + +"detect-kerning@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-kerning@npm:2.1.2" + checksum: 10c0/290f31729f4fc900fc0755a1dd5c5b210b03845b7b0b4ebe2ee29a19a905d7aac1d17adfb700e5ee3ea632cb248cc0acab3280a513b82633c1089daebdcf064d + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10c0/c38cfc8eeb9fda09febb44bcd85e467c970d4e3bf526095394e5a4f18bc26dd0cf6b22c69c1fa9969261521c593836db335c2795218f6d781a512aea2fb8209d + languageName: node + linkType: hard + +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: "npm:^2.0.0" + checksum: 10c0/e0928ab8f94c59417a2b8389c45c55ce0a02d9ac7fd74ef62d01ba48060129e1d594501b77de01f3eeafc7cb00773819b0df74d96251cf20b31c5b3071f45c0e + languageName: node + linkType: hard + +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10c0/32e27ac7dbffdf2fb0eb5a84efd98a9ad084fbabd5ac9abb8757c6770d5320d2acd172830b28c4add29bb873d59420601dfc805ac4064330ce59b1adfd0593b2 + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10c0/81b91f9d39c4eaca068eb0c1eb0e4afbdc5bb2941d197f513dd596b820b956fef43485876226d65d497bebc15666aa2aa82c679e84f65d5f2bfbf14ee46e32c1 + languageName: node + linkType: hard + +"dom-accessibility-api@npm:^0.5.9": + version: 0.5.16 + resolution: "dom-accessibility-api@npm:0.5.16" + checksum: 10c0/b2c2eda4fae568977cdac27a9f0c001edf4f95a6a6191dfa611e3721db2478d1badc01db5bb4fa8a848aeee13e442a6c2a4386d65ec65a1436f24715a2f8d053 + languageName: node + linkType: hard + +"domexception@npm:^4.0.0": + version: 4.0.0 + resolution: "domexception@npm:4.0.0" + dependencies: + webidl-conversions: "npm:^7.0.0" + checksum: 10c0/774277cd9d4df033f852196e3c0077a34dbd15a96baa4d166e0e47138a80f4c0bdf0d94e4703e6ff5883cec56bb821a6fff84402d8a498e31de7c87eb932a294 + languageName: node + linkType: hard + +"dompurify@npm:*": + version: 3.2.4 + resolution: "dompurify@npm:3.2.4" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10c0/6be56810fb7ad2776155c8fc2967af5056783c030094362c7d0cf1ad13f2129cf922d8eefab528a34bdebfb98e2f44b306a983ab93aefb9d6f24c18a3d027a05 + languageName: node + linkType: hard + +"dompurify@npm:^3.3.1, dompurify@npm:^3.4.1": + version: 3.4.1 + resolution: "dompurify@npm:3.4.1" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10c0/440a4246020cb54b82ef2cb66381e4d310a0c470f9994655e61b6122811f0eaa00a2f9665d2bed30ca3f58932ba7bb0d5e26fdfce61c8daf35d21a3f5a799e48 + languageName: node + linkType: hard + +"dot-case@npm:^3.0.4": + version: 3.0.4 + resolution: "dot-case@npm:3.0.4" + dependencies: + no-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/5b859ea65097a7ea870e2c91b5768b72ddf7fa947223fd29e167bcdff58fe731d941c48e47a38ec8aa8e43044c8fbd15cd8fa21689a526bc34b6548197cd5b05 + languageName: node + linkType: hard + +"downshift@npm:9.3.2": + version: 9.3.2 + resolution: "downshift@npm:9.3.2" + dependencies: + "@babel/runtime": "npm:^7.28.6" + compute-scroll-into-view: "npm:^3.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^18.2.0" + tslib: "npm:^2.8.1" + peerDependencies: + react: ">=16.12.0" + checksum: 10c0/e9fa3276c087f3c343b53ed31cb539e9cf237439ea584788e4ef6943dab6bfc870e453ec1d7622fc19e1dcc8677d27f17fcd5db6ce10ca9b37fe3c7b01884090 + languageName: node + linkType: hard + +"draw-svg-path@npm:^1.0.0": + version: 1.0.0 + resolution: "draw-svg-path@npm:1.0.0" + dependencies: + abs-svg-path: "npm:~0.1.1" + normalize-svg-path: "npm:~0.1.0" + checksum: 10c0/659482ae77a013940002292d25a42ff6a4817031ae705e1e9682c7c0b81616081c2ff4a982cd52fba2fbd8f3814aea0875c2831b91a73a85f631b9db1c3fc83d + languageName: node + linkType: hard + +"dtype@npm:^2.0.0": + version: 2.0.0 + resolution: "dtype@npm:2.0.0" + checksum: 10c0/f1478e537d4e90b8d99f0c4b4f28daf588f9b1a9a314eb137a582ab407e64e4867382cbd73cd87892f81c9f195e519d03b9017a3c47bba2cff3e706b9e6a18e5 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"dup@npm:^1.0.0": + version: 1.0.0 + resolution: "dup@npm:1.0.0" + checksum: 10c0/990cde73bbecf90ff58101d2fd96016930fbefd03f047a041a3cc83fcfa22e04c7a02ee4747707b5dabe87fc3a78814d95a9b20d9e6dc98738bd2388efde6f64 + languageName: node + linkType: hard + +"duplexify@npm:^3.4.5": + version: 3.7.1 + resolution: "duplexify@npm:3.7.1" + dependencies: + end-of-stream: "npm:^1.0.0" + inherits: "npm:^2.0.1" + readable-stream: "npm:^2.0.0" + stream-shift: "npm:^1.0.0" + checksum: 10c0/59d1440c1b4e3a4db35ae96933392703ce83518db1828d06b9b6322920d6cbbf0b7159e88be120385fe459e77f1eb0c7622f26e9ec1f47c9ff05c2b35747dbd3 + languageName: node + linkType: hard + +"earcut@npm:^2.1.5, earcut@npm:^2.2.2": + version: 2.2.4 + resolution: "earcut@npm:2.2.4" + checksum: 10c0/01ca51830edd2787819f904ae580087d37351f6048b4565e7add4b3da8a86b7bc19262ab2aa7fdc64129ab03af2d9cec8cccee4d230c82275f97ef285c79aafb + languageName: node + linkType: hard + +"earcut@npm:^3.0.0": + version: 3.0.1 + resolution: "earcut@npm:3.0.1" + checksum: 10c0/cc1d3ed87a08db1a601b3b836bfb2fe22c8f923dfcfcd7419c64a5d4c270ac6d966d8d1e673cc699f8d69dc32bce84fd6d87136c49431fa35057bd7141627769 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10c0/26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.73": + version: 1.5.97 + resolution: "electron-to-chromium@npm:1.5.97" + checksum: 10c0/79080e3fea02158d2a05c881d84711d940817782179f08911f42ad10569832f402e7a612024ebf8c195439f713a4d913689b713f06f010f4cadccf9cf63cd3ad + languageName: node + linkType: hard + +"element-size@npm:^1.1.1": + version: 1.1.1 + resolution: "element-size@npm:1.1.1" + checksum: 10c0/517560f4e0b2dbd0adfe5660bb2572625f605a6268e7983cf96c7584538bef8733fd34e213dc5f653eaea9db5c38069d6e7a3ab960f03c460bcd51e42ee1554d + languageName: node + linkType: hard + +"elementary-circuits-directed-graph@npm:^1.0.4": + version: 1.3.1 + resolution: "elementary-circuits-directed-graph@npm:1.3.1" + dependencies: + strongly-connected-components: "npm:^1.0.1" + checksum: 10c0/a6e8ee9e5724b8d15841e8d456340b25a46c2a501cef2b60a46f1a1808dfade47f2027c4a5e37be6ddafd31a78c3b58e01b12a4f6166649f89e2c9a52507d481 + languageName: node + linkType: hard + +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 10c0/1573d0ae29ab34661b6c63251ff8f5facd24ccf6a823f19417ae8ba8c88ea450325788c67f16c99edec8de4b52ce93a10fe441ece389fd156e88ee7dab9bfa35 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10c0/b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10c0/af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.0.0": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/870b423afb2d54bb8d243c63e07c170409d41e20b47eeef0727547aea5740bd6717aca45597a9f2745525667a6b804c1e7bede41f856818faee5806dd9ff3975 + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10c0/b0701c92a10b89afb1cb45bf54a5292c6f008d744eb4382fa559d54775ff31617d1d7bc3ef617575f552e24fad2c7c1a1835948c66b3f3a4be0a6c1f35c883d8 + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.7.0": + version: 5.18.1 + resolution: "enhanced-resolve@npm:5.18.1" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10c0/4cffd9b125225184e2abed9fdf0ed3dbd2224c873b165d0838fd066cde32e0918626cba2f1f4bf6860762f13a7e2364fd89a82b99566be2873d813573ac71846 + languageName: node + linkType: hard + +"entities@npm:^4.4.0, entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^2.0.0": + version: 2.0.0 + resolution: "es-module-lexer@npm:2.0.0" + checksum: 10c0/ae78dbbd43035a4b972c46cfb6877e374ea290adfc62bc2f5a083fea242c0b2baaab25c5886af86be55f092f4a326741cb94334cd3c478c383fdc8a9ec5ff817 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es5-ext@npm:^0.10.35, es5-ext@npm:^0.10.46, es5-ext@npm:^0.10.62, es5-ext@npm:^0.10.64, es5-ext@npm:~0.10.14": + version: 0.10.64 + resolution: "es5-ext@npm:0.10.64" + dependencies: + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.3" + esniff: "npm:^2.0.1" + next-tick: "npm:^1.1.0" + checksum: 10c0/4459b6ae216f3c615db086e02437bdfde851515a101577fd61b19f9b3c1ad924bab4d197981eb7f0ccb915f643f2fc10ff76b97a680e96cbb572d15a27acd9a3 + languageName: node + linkType: hard + +"es6-iterator@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-iterator@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.35" + es6-symbol: "npm:^3.1.1" + checksum: 10c0/91f20b799dba28fb05bf623c31857fc1524a0f1c444903beccaf8929ad196c8c9ded233e5ac7214fc63a92b3f25b64b7f2737fcca8b1f92d2d96cf3ac902f5d8 + languageName: node + linkType: hard + +"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3": + version: 3.1.4 + resolution: "es6-symbol@npm:3.1.4" + dependencies: + d: "npm:^1.0.2" + ext: "npm:^1.7.0" + checksum: 10c0/777bf3388db5d7919e09a0fd175aa5b8a62385b17cb2227b7a137680cba62b4d9f6193319a102642aa23d5840d38a62e4784f19cfa5be4a2210a3f0e9b23d15d + languageName: node + linkType: hard + +"es6-weak-map@npm:^2.0.3": + version: 2.0.3 + resolution: "es6-weak-map@npm:2.0.3" + dependencies: + d: "npm:1" + es5-ext: "npm:^0.10.46" + es6-iterator: "npm:^2.0.3" + es6-symbol: "npm:^3.1.1" + checksum: 10c0/460932be9542473dbbddd183e21c15a66cfec1b2c17dae2b514e190d6fb2896b7eb683783d4b36da036609d2e1c93d2815f21b374dfccaf02a8978694c2f7b67 + languageName: node + linkType: hard + +"esbuild@npm:^0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^5.0.0": + version: 5.0.0 + resolution: "escape-string-regexp@npm:5.0.0" + checksum: 10c0/6366f474c6f37a802800a435232395e04e9885919873e382b157ab7e8f0feb8fed71497f84a6f6a81a49aab41815522f5839112bd38026d203aea0c91622df95 + languageName: node + linkType: hard + +"escodegen@npm:^2.0.0, escodegen@npm:^2.1.0": + version: 2.1.0 + resolution: "escodegen@npm:2.1.0" + dependencies: + esprima: "npm:^4.0.1" + estraverse: "npm:^5.2.0" + esutils: "npm:^2.0.2" + source-map: "npm:~0.6.1" + dependenciesMeta: + source-map: + optional: true + bin: + escodegen: bin/escodegen.js + esgenerate: bin/esgenerate.js + checksum: 10c0/e1450a1f75f67d35c061bf0d60888b15f62ab63aef9df1901cffc81cffbbb9e8b3de237c5502cf8613a017c1df3a3003881307c78835a1ab54d8c8d2206e01d3 + languageName: node + linkType: hard + +"esniff@npm:^2.0.1": + version: 2.0.1 + resolution: "esniff@npm:2.0.1" + dependencies: + d: "npm:^1.0.1" + es5-ext: "npm:^0.10.62" + event-emitter: "npm:^0.3.5" + type: "npm:^2.7.2" + checksum: 10c0/7efd8d44ac20e5db8cb0ca77eb65eca60628b2d0f3a1030bcb05e71cc40e6e2935c47b87dba3c733db12925aa5b897f8e0e7a567a2c274206f184da676ea2e65 + languageName: node + linkType: hard + +"esprima@npm:^4.0.0, esprima@npm:^4.0.1": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10c0/ad4bab9ead0808cf56501750fd9d3fb276f6b105f987707d059005d57e182d18a7c9ec7f3a01794ebddcca676773e42ca48a32d67a250c9d35e009ca613caba3 + languageName: node + linkType: hard + +"estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: 10c0/d1881c6ed14bd588ebd508fc90bf2a541811dbb9ca04dec2f39d27dcaa635f85b5ed9bbbe7fc6fb1ddfca68744a5f7c70456b4b7108b6c4c52780631cc787c5b + languageName: node + linkType: hard + +"estree-walker@npm:^2.0.2": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 10c0/53a6c54e2019b8c914dc395890153ffdc2322781acf4bd7d1a32d7aedc1710807bdcd866ac133903d5629ec601fbb50abe8c2e5553c7f5a0afdd9b6af6c945af + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"event-emitter@npm:^0.3.5": + version: 0.3.5 + resolution: "event-emitter@npm:0.3.5" + dependencies: + d: "npm:1" + es5-ext: "npm:~0.10.14" + checksum: 10c0/75082fa8ffb3929766d0f0a063bfd6046bd2a80bea2666ebaa0cfd6f4a9116be6647c15667bea77222afc12f5b4071b68d393cf39fdaa0e8e81eda006160aff0 + languageName: node + linkType: hard + +"events@npm:^3.2.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10c0/d6b6f2adbccbcda74ddbab52ed07db727ef52e31a61ed26db9feb7dc62af7fc8e060defa65e5f8af9449b86b52cc1a1f6a79f2eafcf4e62add2b7a1fa4a432f6 + languageName: node + linkType: hard + +"example@workspace:example": + version: 0.0.0-use.local + resolution: "example@workspace:example" + dependencies: + "@biomejs/biome": "npm:2.4.13" + "@development-framework/dm-core": "npm:^1.46.0" + "@development-framework/dm-core-plugins": "npm:^1.57.0" + "@types/node": "npm:^25.6.0" + "@types/react": "npm:^19.2.14" + "@types/react-dom": "npm:^19.2.3" + "@types/react-plotly.js": "npm:^2.6.4" + "@types/react-router-dom": "npm:^5.3.3" + "@types/styled-components": "npm:^5.1.36" + "@vitejs/plugin-react-swc": "npm:^4.3.0" + plotly.js: "npm:^3.5.0" + react: "npm:^19.2.5" + react-dom: "npm:^19.2.5" + react-oauth2-code-pkce: "npm:^1.24.0" + react-plotly.js: "npm:^2.6.0" + react-router-dom: "npm:^7.14.2" + react-toastify: "npm:^11.1.0" + serve: "npm:14.2.6" + styled-components: "npm:^6.4.1" + ts-node: "npm:^10.9.2" + tsconfig-paths-webpack-plugin: "npm:^4.2.0" + typescript: "npm:^5.9.3" + vite: "npm:^7.3.1" + vite-plugin-checker: "npm:^0.13.0" + vite-plugin-svgr: "npm:^4.5.0" + vite-tsconfig-paths: "npm:^6.1.1" + vitest: "npm:^4.1.5" + languageName: unknown + linkType: soft + +"execa@npm:^1.0.0": + version: 1.0.0 + resolution: "execa@npm:1.0.0" + dependencies: + cross-spawn: "npm:^6.0.0" + get-stream: "npm:^4.0.0" + is-stream: "npm:^1.1.0" + npm-run-path: "npm:^2.0.0" + p-finally: "npm:^1.0.0" + signal-exit: "npm:^3.0.0" + strip-eof: "npm:^1.0.0" + checksum: 10c0/cc71707c9aa4a2552346893ee63198bf70a04b5a1bc4f8a0ef40f1d03c319eae80932c59191f037990d7d102193e83a38ec72115fff814ec2fb3099f3661a590 + languageName: node + linkType: hard + +"execa@npm:^5.0.0, execa@npm:^5.1.1": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10c0/71d2ad9b36bc25bb8b104b17e830b40a08989be7f7d100b13269aaae7c3784c3e6e1e88a797e9e87523993a25ba27c8958959a554535370672cfb4d824af8989 + languageName: node + linkType: hard + +"expect-type@npm:^1.3.0": + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/2eddeace66e68b8d8ee5f7be57f3014b19770caaf6815c7a08d131821da527fb8c8cb7b3dcd7c883d2d3d8d184206a4268984618032d1e4b16dc8d6596475d41 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"ext@npm:^1.7.0": + version: 1.7.0 + resolution: "ext@npm:1.7.0" + dependencies: + type: "npm:^2.7.2" + checksum: 10c0/a8e5f34e12214e9eee3a4af3b5c9d05ba048f28996450975b369fc86e5d0ef13b6df0615f892f5396a9c65d616213c25ec5b0ad17ef42eac4a500512a19da6c7 + languageName: node + linkType: hard + +"extend@npm:^3.0.0": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10c0/73bf6e27406e80aa3e85b0d1c4fd987261e628064e170ca781125c0b635a3dabad5e05adbf07595ea0cf1e6c5396cacb214af933da7cbaf24fe75ff14818e8f9 + languageName: node + linkType: hard + +"falafel@npm:^2.1.0": + version: 2.2.5 + resolution: "falafel@npm:2.2.5" + dependencies: + acorn: "npm:^7.1.1" + isarray: "npm:^2.0.1" + checksum: 10c0/8c65a82d6b1d84928373bd1f4ba7452bce2936b3761558d770163b637930db70a9f79f296caaf6b3c63262cb0c9501a727061ee75224fb7a5a9334dadd1ff787 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.2": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe + languageName: node + linkType: hard + +"fast-isnumeric@npm:^1.1.4": + version: 1.1.4 + resolution: "fast-isnumeric@npm:1.1.4" + dependencies: + is-string-blank: "npm:^1.0.1" + checksum: 10c0/595da81fcabe7bebd62429a4fcc5b150aa576a744209e769c9c8cd2a1f0d2db546ab71df9de6f50a41768fa2daf35171b8270a0c3aa25235022a10ebda7dcec8 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.1.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10c0/44364adca566f70f40d1e9b772c923138d47efeac2ae9732a872baafd77061f26b097ba2f68f0892885ad177becd065520412b8ffeec34b16c99433c5b9e2de7 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.19.0 + resolution: "fastq@npm:1.19.0" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10c0/d6a001638f1574a696660fcbba5300d017760432372c801632c325ca7c16819604841c92fd3ccadcdacec0966ca336363a5ff57bc5f0be335d8ea7ac6087b98f + languageName: node + linkType: hard + +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: "npm:2.1.1" + checksum: 10c0/feae89ac148adb8f6ae8ccd87632e62b13563e6fb114cacb5265c51f585b17e2e268084519fb2edd133872f1d47a18e6bfd7e5e08625c0d41b93149694187581 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + +"flatten-vertex-data@npm:^1.0.2": + version: 1.0.2 + resolution: "flatten-vertex-data@npm:1.0.2" + dependencies: + dtype: "npm:^2.0.0" + checksum: 10c0/ef4e03483da4cc839f9f30f9ad419143ace7b9867fbfc55d87c62dfbd613ce99388628995f14580230292d38147c48e1b41cd4d574b2cd86ad63c6ca18e955bf + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.11": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/d301f430542520a54058d4aeeb453233c564aaccac835d29d15e050beb33f339ad67d9bddbce01739c5dc46a6716dbe3d9d0d5134b1ca203effa11a7ef092343 + languageName: node + linkType: hard + +"font-atlas@npm:^2.1.0": + version: 2.1.0 + resolution: "font-atlas@npm:2.1.0" + dependencies: + css-font: "npm:^1.0.0" + checksum: 10c0/b012db553a168de977fbdebd3bd36f29fc7686d2382198ec84c81e45f55356002736f05045ed35acebc6c1277688f4322f7c979e9a58a3aecf3cfb681eac5665 + languageName: node + linkType: hard + +"font-measure@npm:^1.2.2": + version: 1.2.2 + resolution: "font-measure@npm:1.2.2" + dependencies: + css-font: "npm:^1.2.0" + checksum: 10c0/97c4590b91078cce928a6ba4b8762b5a3fb6eedf4ecf59aeccd93c0031f3a952360221622802ee41e737d720c8f385520d4467c650a7d682924085bc782af8d5 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.1 + resolution: "form-data@npm:4.0.1" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + mime-types: "npm:^2.1.12" + checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 + languageName: node + linkType: hard + +"form-data@npm:^4.0.5": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10c0/dd6b767ee0bbd6d84039db12a0fa5a2028160ffbfaba1800695713b46ae974a5f6e08b3356c3195137f8530dcd9dfcb5d5ae1eeff53d0db1e5aad863b619ce3b + languageName: node + linkType: hard + +"frac@npm:~1.1.2": + version: 1.1.2 + resolution: "frac@npm:1.1.2" + checksum: 10c0/640740eb58b590eb38c78c676955bee91cd22d854f5876241a15c49d4495fa53a84898779dcf7eca30aabfe1c1a4a705752b5f224934257c5dda55c545413ba7 + languageName: node + linkType: hard + +"from2@npm:^2.3.0": + version: 2.3.0 + resolution: "from2@npm:2.3.0" + dependencies: + inherits: "npm:^2.0.1" + readable-stream: "npm:^2.0.0" + checksum: 10c0/f87f7a2e4513244d551454a7f8324ef1f7837864a8701c536417286ec19ff4915606b1dfa8909a21b7591ebd8440ffde3642f7c303690b9a4d7c832d62248aa1 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"geojson-vt@npm:^3.2.1": + version: 3.2.1 + resolution: "geojson-vt@npm:3.2.1" + checksum: 10c0/db2fc1a452067ee8436fa86e5a138f6ebd3d64893e0af097bc1cc960ec63d67c0ce77444711e9583036192d6bf9ce754bf9b56a76789684fc0fea4d52321fffc + languageName: node + linkType: hard + +"geojson-vt@npm:^4.0.2": + version: 4.0.2 + resolution: "geojson-vt@npm:4.0.2" + checksum: 10c0/f2ca14d868e46f6262f5d3862f8e38a2418ecf9bd7cd6938a67bf87f1e2f8fbf65345d95996711b07cdf8f05ef512be0e2c20f0a8179c6393c2fd41badcb0532 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + +"get-canvas-context@npm:^1.0.1": + version: 1.0.2 + resolution: "get-canvas-context@npm:1.0.2" + checksum: 10c0/bfb1eefb3ecce1ffcc7b5bffd729c5b7c9873e8cd52ac1f9c3592a11d08e4ffae296a2dfb69039df91a687233b4e989729e888754ab7a1d10d14b269caea463b + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.6": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: 10c0/e34cdf447fdf1902a1f6d5af737eaadf606d2ee3518287abde8910e04159368c268568174b2e71102b87b26c2020486f126bfca9c4fb1ceb986ff99b52ecd1be + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stream@npm:^4.0.0": + version: 4.1.0 + resolution: "get-stream@npm:4.1.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10c0/294d876f667694a5ca23f0ca2156de67da950433b6fb53024833733975d32582896dbc7f257842d331809979efccf04d5e0b6b75ad4d45744c45f193fd497539 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"gl-mat4@npm:^1.2.0": + version: 1.2.0 + resolution: "gl-mat4@npm:1.2.0" + checksum: 10c0/0cbff006c65bf2b72fae325a10f9940ebf8d1f4c92337efb10c0322a61e03a1921ec5a8f330738fe06fe66f8757e12df0c914fd05155436b9ab5263ccc8b0f66 + languageName: node + linkType: hard + +"gl-matrix@npm:^3.2.1, gl-matrix@npm:^3.4.3": + version: 3.4.3 + resolution: "gl-matrix@npm:3.4.3" + checksum: 10c0/c8ee6e2ce2d089b4ba4ae13ec9d4cb99bf2abe5f68f0cb08d94bbd8bafbec13aacc7230b86539ce5ca01b79226ea8c3194f971f5ca0c81838bc5e4e619dc398e + languageName: node + linkType: hard + +"gl-text@npm:^1.4.0": + version: 1.4.0 + resolution: "gl-text@npm:1.4.0" + dependencies: + bit-twiddle: "npm:^1.0.2" + color-normalize: "npm:^1.5.0" + css-font: "npm:^1.2.0" + detect-kerning: "npm:^2.1.2" + es6-weak-map: "npm:^2.0.3" + flatten-vertex-data: "npm:^1.0.2" + font-atlas: "npm:^2.1.0" + font-measure: "npm:^1.2.2" + gl-util: "npm:^3.1.2" + is-plain-obj: "npm:^1.1.0" + object-assign: "npm:^4.1.1" + parse-rect: "npm:^1.2.0" + parse-unit: "npm:^1.0.1" + pick-by-alias: "npm:^1.2.0" + regl: "npm:^2.0.0" + to-px: "npm:^1.0.1" + typedarray-pool: "npm:^1.1.0" + checksum: 10c0/bb694f69bd6b8c6186482990c12a3d07c54fc92d7c13623ca3cffd3c564045336e7db00d638623517dbd1678cf25986515f6db8f8a984b119867c60bfab6069d + languageName: node + linkType: hard + +"gl-util@npm:^3.1.2": + version: 3.1.3 + resolution: "gl-util@npm:3.1.3" + dependencies: + is-browser: "npm:^2.0.1" + is-firefox: "npm:^1.0.3" + is-plain-obj: "npm:^1.1.0" + number-is-integer: "npm:^1.0.1" + object-assign: "npm:^4.1.0" + pick-by-alias: "npm:^1.2.0" + weak-map: "npm:^1.0.5" + checksum: 10c0/4cff7edbf447d5d6e2260769a46dd9037f631fb6413d0de609d6efe7519b683d3f022f03ee5031079bdf11f276bd2c17c130baffe69a73162fe5f44c88a19d77 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10c0/65676153e2b0c9095100fe7f25a778bf45608eeb32c6048cf307f579649bcc30353277b3b898a3792602c65764e5baa4f643714dfbdfd64ea271d210c7a425fe + languageName: node + linkType: hard + +"global-prefix@npm:^4.0.0": + version: 4.0.0 + resolution: "global-prefix@npm:4.0.0" + dependencies: + ini: "npm:^4.1.3" + kind-of: "npm:^6.0.3" + which: "npm:^4.0.0" + checksum: 10c0/a757bba494f0542a34e82716450506a076e769e05993a9739aea3bf27c3f710cd5635d0f4c1c242650c0dc133bf20a8e8fc9cfd3d1d1c371717218ef561f1ac4 + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 10c0/758f9f258e7b19226bd8d4af5d3b0dcf7038780fb23d82e6f98932c44e239f884847f1766e8fa9cc5635ccb3204f7fa7314d4408dd4002a5e8ea827b4018f0a1 + languageName: node + linkType: hard + +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 + languageName: node + linkType: hard + +"glsl-inject-defines@npm:^1.0.1": + version: 1.0.3 + resolution: "glsl-inject-defines@npm:1.0.3" + dependencies: + glsl-token-inject-block: "npm:^1.0.0" + glsl-token-string: "npm:^1.0.1" + glsl-tokenizer: "npm:^2.0.2" + checksum: 10c0/a91217705737773ec33123627c679992b085ca810c3b02943834801d8cbbaa892c4a8ae6d44047685fdd0d6c871accca2c64e68c998c10e236c27ea1d5af8fb3 + languageName: node + linkType: hard + +"glsl-resolve@npm:0.0.1": + version: 0.0.1 + resolution: "glsl-resolve@npm:0.0.1" + dependencies: + resolve: "npm:^0.6.1" + xtend: "npm:^2.1.2" + checksum: 10c0/2ae3220dc55af84bd21e4014c4cb0118381221d13419750fd0651e08c1a69d5b0c14c7b3c4362594ab8dcd3a0ae103d206c465b67304e2774ad7e5a7ef382655 + languageName: node + linkType: hard + +"glsl-token-assignments@npm:^2.0.0": + version: 2.0.2 + resolution: "glsl-token-assignments@npm:2.0.2" + checksum: 10c0/da50a255b2c8ced68154a6451e1bb4b9c898399e7751c2084a8f942fd96596701b2281976dec974187c27b5b17ec18affe5fd74ecc4a2e119b427fa07593a4e9 + languageName: node + linkType: hard + +"glsl-token-defines@npm:^1.0.0": + version: 1.0.0 + resolution: "glsl-token-defines@npm:1.0.0" + dependencies: + glsl-tokenizer: "npm:^2.0.0" + checksum: 10c0/a1282c01186ea93a6a398c0af74f8857e82ead6e2d7201353edb88a6e7fd5852a575cc49a5d0e7cf116ca346c32569bb64419a8697cf9be878f5d070ca07bd60 + languageName: node + linkType: hard + +"glsl-token-depth@npm:^1.1.0, glsl-token-depth@npm:^1.1.1": + version: 1.1.2 + resolution: "glsl-token-depth@npm:1.1.2" + checksum: 10c0/e9a476fee487080c2247bb5edde34ee15d027dfe66802ec1cb501d5a0a99964faaa06f2643e575089acd931a917735d3d10045743f0c3e5f1c482de94cb9bd6c + languageName: node + linkType: hard + +"glsl-token-descope@npm:^1.0.2": + version: 1.0.2 + resolution: "glsl-token-descope@npm:1.0.2" + dependencies: + glsl-token-assignments: "npm:^2.0.0" + glsl-token-depth: "npm:^1.1.0" + glsl-token-properties: "npm:^1.0.0" + glsl-token-scope: "npm:^1.1.0" + checksum: 10c0/7018b13194d5cec87d97c3ce0dd64c554fd9d6b547854bc1789dad407c079b5d53445649d930b18c9a1eb8704666c37821ccc796b865fc751ff66c7e5cf04413 + languageName: node + linkType: hard + +"glsl-token-inject-block@npm:^1.0.0": + version: 1.1.0 + resolution: "glsl-token-inject-block@npm:1.1.0" + checksum: 10c0/d5ed326f2b8e016997e201316bca7f02bf38cc75e5b6a965819e0819d67aadd1b5c5f236acacabb125e2c3383b5a9f096ad9aed3fb469bbc5014bc7990797641 + languageName: node + linkType: hard + +"glsl-token-properties@npm:^1.0.0": + version: 1.0.1 + resolution: "glsl-token-properties@npm:1.0.1" + checksum: 10c0/fa63c248648861bb1fde5d49cb000f930c8a3864c02cdc2f7d62ff669a4fdb1458f3ea4fceac993a94440d8a4883395698d225e8d7b18c01a42bf95c997667d4 + languageName: node + linkType: hard + +"glsl-token-scope@npm:^1.1.0, glsl-token-scope@npm:^1.1.1": + version: 1.1.2 + resolution: "glsl-token-scope@npm:1.1.2" + checksum: 10c0/5b5dd2cb78d63ec7c52d3134aabcdf3a0d0e7b1f3d42f6a45169485ff3b31ecba0ec374ceb59a6ebb16e0d5e3b24b2e247faa7bda4e7c56faea02c44110e0ef4 + languageName: node + linkType: hard + +"glsl-token-string@npm:^1.0.1": + version: 1.0.1 + resolution: "glsl-token-string@npm:1.0.1" + checksum: 10c0/01ff4fbfa02956d5b10e96f98ab1066937993b2ff9a2c24d23bf72fd417b59d9849cd856815c84d6170d3715bd064c4fbd1cff540a0e315c1fa9b20f2f3306e6 + languageName: node + linkType: hard + +"glsl-token-whitespace-trim@npm:^1.0.0": + version: 1.0.0 + resolution: "glsl-token-whitespace-trim@npm:1.0.0" + checksum: 10c0/319dd8ff0db0c0109914711845e9fd3eda403ad53c9d499ea5a83167b277390186669242b2d1ca791efa7334ffc7fac58fe2d5172859cd79e51e12df909e5416 + languageName: node + linkType: hard + +"glsl-tokenizer@npm:^2.0.0, glsl-tokenizer@npm:^2.0.2": + version: 2.1.5 + resolution: "glsl-tokenizer@npm:2.1.5" + dependencies: + through2: "npm:^0.6.3" + checksum: 10c0/5bcc4491afb0b09032702af83f0afa8acadfa0f5c94a7a0d7c3498396cc35722e1915c976c8efef80a1a9ef0976ecb8ce4e044a34b16d7d537ae4c80f346ad19 + languageName: node + linkType: hard + +"glslify-bundle@npm:^5.0.0": + version: 5.1.1 + resolution: "glslify-bundle@npm:5.1.1" + dependencies: + glsl-inject-defines: "npm:^1.0.1" + glsl-token-defines: "npm:^1.0.0" + glsl-token-depth: "npm:^1.1.1" + glsl-token-descope: "npm:^1.0.2" + glsl-token-scope: "npm:^1.1.1" + glsl-token-string: "npm:^1.0.1" + glsl-token-whitespace-trim: "npm:^1.0.0" + glsl-tokenizer: "npm:^2.0.2" + murmurhash-js: "npm:^1.0.0" + shallow-copy: "npm:0.0.1" + checksum: 10c0/a3e79b5cd1f7aec444d29b855612ed6884a23b28bdc1656e180cdbc04ab0aad5e5f77a1c776bed335d4e9a0c781fd39295f045fa6c34e358fe7ccbe3606e8d8d + languageName: node + linkType: hard + +"glslify-deps@npm:^1.2.5": + version: 1.3.2 + resolution: "glslify-deps@npm:1.3.2" + dependencies: + "@choojs/findup": "npm:^0.2.0" + events: "npm:^3.2.0" + glsl-resolve: "npm:0.0.1" + glsl-tokenizer: "npm:^2.0.0" + graceful-fs: "npm:^4.1.2" + inherits: "npm:^2.0.1" + map-limit: "npm:0.0.1" + resolve: "npm:^1.0.0" + checksum: 10c0/d89d9ca0bcccb1eb0c2f31d21a0fad9592df5faa30fa63416064cfc4776a3ea38c9e0da2425d2ac5a5a5625012baac95a0bd056e3a525bbf1d149894c140b00f + languageName: node + linkType: hard + +"glslify@npm:^7.0.0": + version: 7.1.1 + resolution: "glslify@npm:7.1.1" + dependencies: + bl: "npm:^2.2.1" + concat-stream: "npm:^1.5.2" + duplexify: "npm:^3.4.5" + falafel: "npm:^2.1.0" + from2: "npm:^2.3.0" + glsl-resolve: "npm:0.0.1" + glsl-token-whitespace-trim: "npm:^1.0.0" + glslify-bundle: "npm:^5.0.0" + glslify-deps: "npm:^1.2.5" + minimist: "npm:^1.2.5" + resolve: "npm:^1.1.5" + stack-trace: "npm:0.0.9" + static-eval: "npm:^2.0.5" + through2: "npm:^2.0.1" + xtend: "npm:^4.0.0" + bin: + glslify: bin.js + checksum: 10c0/ccf277220047752ee9ddfb8e1398846a747ca8eb6601a71c4e34278b2e15845affbb52d4ef0023cb21bb0f52785b585c5af9189cc2f0abfca1ffab15c1b3f6f6 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"grid-index@npm:^1.1.0": + version: 1.1.0 + resolution: "grid-index@npm:1.1.0" + checksum: 10c0/0ba2a622a52badc86642a002abee79b48c207092347e869528253e634573b9b55494db649fbd1779122d797aaa3b59e284023a96a7a5c666c7375f0e320f8f57 + languageName: node + linkType: hard + +"hachure-fill@npm:^0.5.2": + version: 0.5.2 + resolution: "hachure-fill@npm:0.5.2" + checksum: 10c0/307e3b6f9f2d3c11a82099c3f71eecbb9c440c00c1f896ac1732c23e6dbff16a92bb893d222b8b721b89cf11e58649ca60b4c24e5663f705f877cefd40153429 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.9": + version: 4.7.9 + resolution: "handlebars@npm:4.7.9" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10c0/22f8105a7e68e81aff2662bb434edf05f757d21d850731d71cec886d69c10cd33d3c43e34b2892968ec62de8241611851d3d0674c8ef324ea3e01dc66262faa9 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-hover@npm:^1.0.1": + version: 1.0.1 + resolution: "has-hover@npm:1.0.1" + dependencies: + is-browser: "npm:^2.0.1" + checksum: 10c0/c3abd4be00fc20b3e3ff84def23330174b13c46c76831b712444806421f2d9ac1d7d46c5e94bb207ea893a7b765ce0d5b92b3e1d741fc83d182bdd8e678b4d70 + languageName: node + linkType: hard + +"has-passive-events@npm:^1.0.0": + version: 1.0.0 + resolution: "has-passive-events@npm:1.0.0" + dependencies: + is-browser: "npm:^2.0.1" + checksum: 10c0/438e63c0c66274559bc74b2b4f8c1e93e8779caceee27141b59c87b984765b39d5fc5cf0f709a5ecc915cdc1138cf3d206daeece15fcfb41ec1c721ddbae05fc + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hast-util-to-jsx-runtime@npm:^2.0.0": + version: 2.3.6 + resolution: "hast-util-to-jsx-runtime@npm:2.3.6" + dependencies: + "@types/estree": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/unist": "npm:^3.0.0" + comma-separated-tokens: "npm:^2.0.0" + devlop: "npm:^1.0.0" + estree-util-is-identifier-name: "npm:^3.0.0" + hast-util-whitespace: "npm:^3.0.0" + mdast-util-mdx-expression: "npm:^2.0.0" + mdast-util-mdx-jsx: "npm:^3.0.0" + mdast-util-mdxjs-esm: "npm:^2.0.0" + property-information: "npm:^7.0.0" + space-separated-tokens: "npm:^2.0.0" + style-to-js: "npm:^1.0.0" + unist-util-position: "npm:^5.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/27297e02848fe37ef219be04a26ce708d17278a175a807689e94a821dcffc88aa506d62c3a85beed1f9a8544f7211bdcbcde0528b7b456a57c2e342c3fd11056 + languageName: node + linkType: hard + +"hast-util-whitespace@npm:^3.0.0": + version: 3.0.0 + resolution: "hast-util-whitespace@npm:3.0.0" + dependencies: + "@types/hast": "npm:^3.0.0" + checksum: 10c0/b898bc9fe27884b272580d15260b6bbdabe239973a147e97fa98c45fa0ffec967a481aaa42291ec34fb56530dc2d484d473d7e2bae79f39c83f3762307edfea8 + languageName: node + linkType: hard + +"highlight.js@npm:^11.11.1": + version: 11.11.1 + resolution: "highlight.js@npm:11.11.1" + checksum: 10c0/40f53ac19dac079891fcefd5bd8a21cf2e8931fd47da5bd1dca73b7e4375c1defed0636fc39120c639b9c44119b7d110f7f0c15aa899557a5a1c8910f3c0144c + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.0": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^3.0.0": + version: 3.0.0 + resolution: "html-encoding-sniffer@npm:3.0.0" + dependencies: + whatwg-encoding: "npm:^2.0.0" + checksum: 10c0/b17b3b0fb5d061d8eb15121c3b0b536376c3e295ecaf09ba48dd69c6b6c957839db124fe1e2b3f11329753a4ee01aa7dedf63b7677999e86da17fbbdd82c5386 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"html-url-attributes@npm:^3.0.0": + version: 3.0.1 + resolution: "html-url-attributes@npm:3.0.1" + checksum: 10c0/496e4908aa8b77665f348b4b03521901794f648b8ac34a581022cd6f2c97934d5c910cd91bc6593bbf2994687549037bc2520fcdc769b31484f29ffdd402acd0 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": "npm:2" + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/32a05e413430b2c1e542e5c74b38a9f14865301dd69dff2e53ddb684989440e3d2ce0c4b64d25eb63cf6283e6265ff979a61cf93e3ca3d23047ddfdc8df34a32 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"iconv-lite@npm:0.6, iconv-lite@npm:0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.4.4": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10c0/c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.12": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"import-fresh@npm:^3.3.0": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"import-local@npm:^3.0.2": + version: 3.2.0 + resolution: "import-local@npm:3.2.0" + dependencies: + pkg-dir: "npm:^4.2.0" + resolve-cwd: "npm:^3.0.0" + bin: + import-local-fixture: fixtures/cli.js + checksum: 10c0/94cd6367a672b7e0cb026970c85b76902d2710a64896fa6de93bd5c571dd03b228c5759308959de205083e3b1c61e799f019c9e36ee8e9c523b993e1057f0433 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:~2.0.1, inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ini@npm:^4.1.3": + version: 4.1.3 + resolution: "ini@npm:4.1.3" + checksum: 10c0/0d27eff094d5f3899dd7c00d0c04ea733ca03a8eb6f9406ce15daac1a81de022cb417d6eaff7e4342451ffa663389c565ffc68d6825eaf686bf003280b945764 + languageName: node + linkType: hard + +"ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a + languageName: node + linkType: hard + +"inline-style-parser@npm:0.2.4": + version: 0.2.4 + resolution: "inline-style-parser@npm:0.2.4" + checksum: 10c0/ddc0b210eaa03e0f98d677b9836242c583c7c6051e84ce0e704ae4626e7871c5b78f8e30853480218b446355745775df318d4f82d33087ff7e393245efa9a881 + languageName: node + linkType: hard + +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 10c0/8cedd57f07bbc22501516fbfc70447f0c6812871d471096fad9ea603516eacc2137b633633daf432c029712df0baefd793686388ddf5737e3ea15074b877f7ed + languageName: node + linkType: hard + +"internmap@npm:^1.0.0": + version: 1.0.1 + resolution: "internmap@npm:1.0.1" + checksum: 10c0/60942be815ca19da643b6d4f23bd0bf4e8c97abbd080fb963fe67583b60bdfb3530448ad4486bae40810e92317bded9995cc31411218acc750d72cd4e8646eee + languageName: node + linkType: hard + +"interpret@npm:^1.0.0": + version: 1.4.0 + resolution: "interpret@npm:1.4.0" + checksum: 10c0/08c5ad30032edeec638485bc3f6db7d0094d9b3e85e0f950866600af3c52e9fd69715416d29564731c479d9f4d43ff3e4d302a178196bdc0e6837ec147640450 + languageName: node + linkType: hard + +"is-alphabetical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphabetical@npm:2.0.1" + checksum: 10c0/932367456f17237533fd1fc9fe179df77957271020b83ea31da50e5cc472d35ef6b5fb8147453274ffd251134472ce24eb6f8d8398d96dee98237cdb81a6c9a7 + languageName: node + linkType: hard + +"is-alphanumerical@npm:^2.0.0": + version: 2.0.1 + resolution: "is-alphanumerical@npm:2.0.1" + dependencies: + is-alphabetical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + checksum: 10c0/4b35c42b18e40d41378293f82a3ecd9de77049b476f748db5697c297f686e1e05b072a6aaae2d16f54d2a57f85b00cbbe755c75f6d583d1c77d6657bd0feb5a2 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-browser@npm:^2.0.1": + version: 2.1.0 + resolution: "is-browser@npm:2.1.0" + checksum: 10c0/107cb5211009823df2c3001e419bd9806472f9f2ca81e87b2e60b36c0a4ac709dc5f093d415c372d37758a39f22c98fd5bff060c1d0cfbb74c694b41d3df75c9 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.0": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-decimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-decimal@npm:2.0.1" + checksum: 10c0/8085dd66f7d82f9de818fba48b9e9c0429cb4291824e6c5f2622e96b9680b54a07a624cfc663b24148b8e853c62a1c987cfe8b0b5a13f5156991afaf6736e334 + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10c0/e828365958d155f90c409cdbe958f64051d99e8aedc2c8c4cd7c89dcf35329daed42f7b99346f7828df013e27deb8f721cf9408ba878c76eb9e8290235fbcdcc + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finite@npm:^1.0.1": + version: 1.1.0 + resolution: "is-finite@npm:1.1.0" + checksum: 10c0/ca6bc7a0321b339f098e657bd4cbf4bb2410f5a11f1b9adb1a1a9ab72288b64368e8251326cb1f74e985f2779299cec3e1f1e558b68ce7e1e2c9be17b7cfd626 + languageName: node + linkType: hard + +"is-firefox@npm:^1.0.3": + version: 1.0.3 + resolution: "is-firefox@npm:1.0.3" + checksum: 10c0/05acae8ad7f836088dd735f78c2da298f6b5bf2141498b214a370f4a99a22f837a130ebb3bce2320ecfcd68783f0eda231a04b3b5015b5894a5a297b16357bc2 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10c0/bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d + languageName: node + linkType: hard + +"is-glob@npm:^4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-hexadecimal@npm:^2.0.0": + version: 2.0.1 + resolution: "is-hexadecimal@npm:2.0.1" + checksum: 10c0/3eb60fe2f1e2bbc760b927dcad4d51eaa0c60138cf7fc671803f66353ad90c301605b502c7ea4c6bb0548e1c7e79dfd37b73b632652e3b76030bba603a7e9626 + languageName: node + linkType: hard + +"is-iexplorer@npm:^1.0.0": + version: 1.0.0 + resolution: "is-iexplorer@npm:1.0.0" + checksum: 10c0/f1e9483fe0675b0a8424f0d4e07fe33508452760f221bad609ff28c8834e8decbdaa080727bb54da8414e0420c5184f761267d86cdddad3897062701f31eb49d + languageName: node + linkType: hard + +"is-mobile@npm:^4.0.0": + version: 4.0.0 + resolution: "is-mobile@npm:4.0.0" + checksum: 10c0/7d1f1c9ead3f140728318df7b1d6f2f19f28d96bf09c3a9016fe473ccccd32c4d03a01aeec68b612d48f1c0f776e7f1f18a1d83a7e95fb8199b4eb8536db01bc + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 + languageName: node + linkType: hard + +"is-obj@npm:^1.0.1": + version: 1.0.1 + resolution: "is-obj@npm:1.0.1" + checksum: 10c0/5003acba0af7aa47dfe0760e545a89bbac89af37c12092c3efadc755372cdaec034f130e7a3653a59eb3c1843cfc72ca71eaf1a6c3bafe5a0bab3611a47f9945 + languageName: node + linkType: hard + +"is-plain-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "is-plain-obj@npm:1.1.0" + checksum: 10c0/daaee1805add26f781b413fdf192fc91d52409583be30ace35c82607d440da63cc4cac0ac55136716688d6c0a2c6ef3edb2254fecbd1fe06056d6bd15975ee8c + languageName: node + linkType: hard + +"is-plain-obj@npm:^4.0.0": + version: 4.1.0 + resolution: "is-plain-obj@npm:4.1.0" + checksum: 10c0/32130d651d71d9564dc88ba7e6fda0e91a1010a3694648e9f4f47bb6080438140696d3e3e15c741411d712e47ac9edc1a8a9de1fe76f3487b0d90be06ac9975e + languageName: node + linkType: hard + +"is-port-reachable@npm:4.0.0": + version: 4.0.0 + resolution: "is-port-reachable@npm:4.0.0" + checksum: 10c0/f0fddd9b5c082f7c32356faab38c3c6eab5ea5b54491184f5688f3189d482017d2142c648927ee5964299e4a62da83d41ee52a1d73bf1f700325c370c9ed0cef + languageName: node + linkType: hard + +"is-potential-custom-element-name@npm:^1.0.1": + version: 1.0.1 + resolution: "is-potential-custom-element-name@npm:1.0.1" + checksum: 10c0/b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 + languageName: node + linkType: hard + +"is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10c0/b8ae7971e78d2e8488d15f804229c6eed7ed36a28f8807a1815938771f4adff0e705218b7dab968270433f67103e4fef98062a0beea55d64835f705ee72c7002 + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-string-blank@npm:^1.0.1": + version: 1.0.1 + resolution: "is-string-blank@npm:1.0.1" + checksum: 10c0/2e664a061fb9354f6eccffdd7d662051d19ce951f59ae25392b871f796e06dc82659fb68560b267ccc81b700c1078c0fb8e31959955d759c2f3713015ccfb60f + languageName: node + linkType: hard + +"is-svg-path@npm:^1.0.1": + version: 1.0.2 + resolution: "is-svg-path@npm:1.0.2" + checksum: 10c0/59596bdb3e3a748e654c9c2362a55234ff7350131de235c49b9794e8bbdbeab57f2f06803ec5dbfb5e65a023967ff1322cc75135b841fa82f88f032e1cb407d5 + languageName: node + linkType: hard + +"is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10c0/a6fa2d370d21be487c0165c7a440d567274fbba1a817f2f0bfa41cc5e3af25041d84267baa22df66696956038a43973e72fca117918c91431920bdef490fa25e + languageName: node + linkType: hard + +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: 10c0/ed1e62da617f71fe348907c71743b5ed550448b455f8d269f89a7c7ddb8ae6e962de3dab6a74a237b06f5eb7f6ece7a45ada8ce96d87fe972926530f91ae3311 + languageName: node + linkType: hard + +"isarray@npm:^2.0.1": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10c0/8a1bdf3e377dcc0d33ec32fe2b6ecacdb1e4358fd0eb923d4326bb11c67622c0ceb99600a680f3dad5d29c66fc1991306081e339b4d43d0b8a2ab2e1d910a6ee + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.3 + resolution: "istanbul-lib-instrument@npm:6.0.3" + dependencies: + "@babel/core": "npm:^7.23.9" + "@babel/parser": "npm:^7.23.9" + "@istanbuljs/schema": "npm:^0.1.3" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^7.5.4" + checksum: 10c0/a1894e060dd2a3b9f046ffdc87b44c00a35516f5e6b7baf4910369acca79e506fc5323a816f811ae23d82334b38e3ddeb8b3b331bd2c860540793b59a8689128 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + languageName: node + linkType: hard + +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + languageName: node + linkType: hard + +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + languageName: node + linkType: hard + +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + languageName: node + linkType: hard + +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/89a4a7f182590f56f526443dde69acefb1f2f0c9e59253c61d319569856c4931eae66b8a3790c443f529267a0ddba5ba80431c585deed81827032b2b2a1fc999 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10c0/d932a8272345cf6b6142bb70a2bb63e0856cc0093f082821577ea5bdf4643916a98744dfc992189d2b1417c38a11fa42466f6111526bc1fb81366f56410f3be9 + languageName: node + linkType: hard + +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + languageName: node + linkType: hard + +"jest-environment-jsdom@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-jsdom@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/jsdom": "npm:^20.0.0" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jsdom: "npm:^20.0.0" + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/139b94e2c8ec1bb5a46ce17df5211da65ce867354b3fd4e00fa6a0d1da95902df4cf7881273fc6ea937e5c325d39d6773f0d41b6c469363334de9d489d2c321f + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10c0/61f04fec077f8b1b5c1a633e3612fc0c9aa79a0ab7b05600683428f1e01a4d35346c474bde6f439f9fcc1a4aa9a2861ff852d079a43ab64b02105d1004b2592b + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10c0/552e7a97a983d3c2d4e412a44eb7de0430ff773dd99f7500962c268d6dfbfa431d7d08f919c9d960530e5f7f78eb47f267ad9b318265e5092b3ff9ede0db7c2b + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/2683a8f29793c75a4728787662972fedd9267704c8f7ef9d84f2beed9a977f1cf5e998c07b6f36ba5603f53cb010c911fe8cd0ac9886e073fe28ca66beefd30c + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/71bb9f77fc489acb842a5c7be030f2b9acb18574dc9fb98b3100fc57d422b1abc55f08040884bd6e6dbf455047a62f7eaff12aa4058f7cbdc11558718ca6a395 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10c0/0d0e70b28fa5c7d4dce701dc1f46ae0922102aadc24ed45d594dd9b7ae0a8a6ef8b216718d1ab79e451291217e05d4d49a82666e1a3cc2b428b75cd9c933244e + languageName: node + linkType: hard + +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10c0/850ae35477f59f3e6f27efac5215f706296e2104af39232bb14e5403e067992afb5c015e87a9243ec4d9df38525ef1ca663af9f2f4766aa116f127247008bd22 + languageName: node + linkType: hard + +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10c0/7b9f8349ee87695a309fe15c46a74ab04c853369e5c40952d68061d9dc3159a0f0ed73e215f81b07ee97a9faaf10aebe5877a9d6255068a0977eae6a9ff1d5ac + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: 10c0/86eec0c78449a2de733a6d3e316d49461af6a858070e113c97f75fb742a48c2396ea94150cbca44159ffd4a959f743a47a8b37a792ef6fdad2cf0a5cba973fac + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10c0/4e33fb16c4f42111159cafe26397118dcfc4cf08bc178a67149fb05f45546a91928b820894572679d62559839d0992e21080a1527faad65daaae8743a5705a3b + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + languageName: node + linkType: hard + +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10c0/59da5c9c5b50563e959a45e09e2eace783d7f9ac0b5dcc6375dea4c0db938d2ebda97124c8161310082760e8ebbeff9f6b177c15ca2f57fb424f637a5d2adb47 + languageName: node + linkType: hard + +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/2194b4531068d939f14c8d3274fe5938b77fa73126aedf9c09ec9dec57d13f22c72a3b5af01ac04f5c1cf2e28d0ac0b4a54212a61b05f10b5d6b47f2a1097bb4 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/7cd89a1deda0bda7d0941835434e44f9d6b7bd50b5c5d9b0fc9a6c990b2d4d2cab59685ab3cb2850ed4cc37059f6de903af5a50565d7f7f1192a77d3fd6dd2a6 + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10c0/6e9003c94ec58172b4a62864a91c0146513207bedf4e0a06e1e2ac70a4484088a2683e3a0538d8ea913bcfd53dc54a9b98a98cdfa562e7fe1d1339aeae1da570 + languageName: node + linkType: hard + +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + graceful-fs: "npm:^4.2.9" + picomatch: "npm:^2.2.3" + checksum: 10c0/bc55a8f49fdbb8f51baf31d2a4f312fb66c9db1483b82f602c9c990e659cdd7ec529c8e916d5a89452ecbcfae4949b21b40a7a59d4ffc0cd813a973ab08c8150 + languageName: node + linkType: hard + +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10c0/a20b930480c1ed68778c739f4739dce39423131bc070cd2505ddede762a5570a256212e9c2401b7ae9ba4d7b7c0803f03c5b8f1561c62348213aba18d9dbece2 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10c0/ec6c75030562fc8f8c727cb8f3b94e75d831fc718785abfc196e1f2a2ebc9a2e38744a15147170039628a853d77a3b695561ce850375ede3a4ee6037a2574567 + languageName: node + linkType: hard + +"jest-worker@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10c0/5570a3a005b16f46c131968b8a5b56d291f9bbb85ff4217e31c80bd8a02e7de799e59a54b95ca28d5c302f248b54cbffde2d177c2f0f52ffcee7504c6eabf660 + languageName: node + linkType: hard + +"jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: "npm:^1.0.7" + esprima: "npm:^4.0.0" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/6746baaaeac312c4db8e75fa22331d9a04cccb7792d126ed8ce6a0bbcfef0cedaddd0c5098fade53db067c09fe00aa1c957674b4765610a8b06a5a189e46433b + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"jsdom@npm:^20.0.0": + version: 20.0.3 + resolution: "jsdom@npm:20.0.3" + dependencies: + abab: "npm:^2.0.6" + acorn: "npm:^8.8.1" + acorn-globals: "npm:^7.0.0" + cssom: "npm:^0.5.0" + cssstyle: "npm:^2.3.0" + data-urls: "npm:^3.0.2" + decimal.js: "npm:^10.4.2" + domexception: "npm:^4.0.0" + escodegen: "npm:^2.0.0" + form-data: "npm:^4.0.0" + html-encoding-sniffer: "npm:^3.0.0" + http-proxy-agent: "npm:^5.0.0" + https-proxy-agent: "npm:^5.0.1" + is-potential-custom-element-name: "npm:^1.0.1" + nwsapi: "npm:^2.2.2" + parse5: "npm:^7.1.1" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^4.1.2" + w3c-xmlserializer: "npm:^4.0.0" + webidl-conversions: "npm:^7.0.0" + whatwg-encoding: "npm:^2.0.0" + whatwg-mimetype: "npm:^3.0.0" + whatwg-url: "npm:^11.0.0" + ws: "npm:^8.11.0" + xml-name-validator: "npm:^4.0.0" + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + checksum: 10c0/b109073bb826a966db7828f46cb1d7371abecd30f182b143c52be5fe1ed84513bbbe995eb3d157241681fcd18331381e61e3dc004d4949f3a63bca02f6214902 + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-loader@npm:^0.5.7": + version: 0.5.7 + resolution: "json-loader@npm:0.5.7" + checksum: 10c0/b155b81f644693b5418e595c127c552c34373f749d2d125df24cfe753de6b3b0af88dda5d58e7b65940ed03ec46c19fc3d09f53b932b45c9ae3ca1fb55e44a15 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"json-stringify-pretty-compact@npm:^4.0.0": + version: 4.0.0 + resolution: "json-stringify-pretty-compact@npm:4.0.0" + checksum: 10c0/505781b4be7c72047ae8dfa667b520d20461ceac451b6516cb8ac5e12a758fbd7491d99d5e3f7e60423ce9d26ed4e4bcaccab3420bf651298901635c849017cf + languageName: node + linkType: hard + +"json5@npm:^2.2.2, json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"katex@npm:^0.16.25": + version: 0.16.45 + resolution: "katex@npm:0.16.45" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 10c0/f715eb9a73daff68ac14dcc8c2f47f02f5fc756a74077d22479e64e06872b2b44f0d81f1c91a98a9873722a08841388a869a0b9ddb96b224362eb8054ddf533a + languageName: node + linkType: hard + +"kdbush@npm:^3.0.0": + version: 3.0.0 + resolution: "kdbush@npm:3.0.0" + checksum: 10c0/3fc8795870bd04f60627e7345b26fd0644beb91bc4164912c9d9378b39c674ba01c31db68ecaf6266d51c9ad81bf5b770b7effa51eeee37553d38293a094a686 + languageName: node + linkType: hard + +"kdbush@npm:^4.0.2": + version: 4.0.2 + resolution: "kdbush@npm:4.0.2" + checksum: 10c0/d50183b299c57e2573114e902ab47aed7494be3ca41b66d456779ecc3b2f153f491de341f9609965414784f728894f9a9001152eb9f3a40cd3755521c06a45a3 + languageName: node + linkType: hard + +"khroma@npm:^2.1.0": + version: 2.1.0 + resolution: "khroma@npm:2.1.0" + checksum: 10c0/634d98753ff5d2540491cafeb708fc98de0d43f4e6795256d5c8f6e3ad77de93049ea41433928fda3697adf7bbe6fe27351858f6d23b78f8b5775ef314c59891 + languageName: node + linkType: hard + +"kind-of@npm:^6.0.3": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"langium@npm:^4.0.0": + version: 4.2.1 + resolution: "langium@npm:4.2.1" + dependencies: + chevrotain: "npm:~11.1.1" + chevrotain-allstar: "npm:~0.3.1" + vscode-languageserver: "npm:~9.0.1" + vscode-languageserver-textdocument: "npm:~1.0.11" + vscode-uri: "npm:~3.1.0" + checksum: 10c0/19ddf79cc3c435ec70f8eb50de255571711db7cea89d171cf80bc97e7ed73d4d0bb6b4215899df8369fa6d0e17f442f30af4ec2e9657041bf2f93be1310ba50a + languageName: node + linkType: hard + +"layout-base@npm:^1.0.0": + version: 1.0.2 + resolution: "layout-base@npm:1.0.2" + checksum: 10c0/2a55d0460fd9f6ed53d7e301b9eb3dea19bda03815d616a40665ce6dc75c1f4d62e1ca19a897da1cfaf6de1b91de59cd6f2f79ba1258f3d7fccc7d46ca7f3337 + languageName: node + linkType: hard + +"layout-base@npm:^2.0.0": + version: 2.0.1 + resolution: "layout-base@npm:2.0.1" + checksum: 10c0/a44df9ef3cbff9916a10f616635e22b5787c89fa62b2fec6f99e8e6ee512c7cebd22668ce32dab5a83c934ba0a309c51a678aa0b40d70853de6c357893c0a88b + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 10c0/cd778ba3fbab0f4d0500b7e87d1f6e1f041507c56fdcd47e8256a3012c98aaee371d4c15e0a76e0386107af2d42e2b7466160a2d80688aaa03e66e49949f42df + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-android-arm64@npm:1.32.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-arm64@npm:1.32.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-darwin-x64@npm:1.32.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-freebsd-x64@npm:1.32.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.32.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.32.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-linux-x64-musl@npm:1.32.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.32.0": + version: 1.32.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.32.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.32.0": + version: 1.32.0 + resolution: "lightningcss@npm:1.32.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.32.0" + lightningcss-darwin-arm64: "npm:1.32.0" + lightningcss-darwin-x64: "npm:1.32.0" + lightningcss-freebsd-x64: "npm:1.32.0" + lightningcss-linux-arm-gnueabihf: "npm:1.32.0" + lightningcss-linux-arm64-gnu: "npm:1.32.0" + lightningcss-linux-arm64-musl: "npm:1.32.0" + lightningcss-linux-x64-gnu: "npm:1.32.0" + lightningcss-linux-x64-musl: "npm:1.32.0" + lightningcss-win32-arm64-msvc: "npm:1.32.0" + lightningcss-win32-x64-msvc: "npm:1.32.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/70945bd55097af46fc9fab7f5ed09cd5869d85940a2acab7ee06d0117004a1d68155708a2d462531cea2fc3c67aefc9333a7068c80b0b78dd404c16838809e03 + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"lodash-es@npm:4.17.23, lodash-es@npm:^4.17.23": + version: 4.17.23 + resolution: "lodash-es@npm:4.17.23" + checksum: 10c0/3150fb6660c14c7a6b5f23bd11597d884b140c0e862a17fdb415aaa5ef7741523182904a6b7929f04e5f60a11edb5a79499eb448734381c99ffb3c4734beeddd + languageName: node + linkType: hard + +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 10c0/fb407355f7e6cd523a9383e76e6b455321f0f153a6c9625e21a8827d10c54c2a2341bd2ae8d034358b60e07325e1330c14c224ff582d04612a46a4f0479ff2f2 + languageName: node + linkType: hard + +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 10c0/c8713e51eccc650422716a14cece1809cfe34bc5ab5e242b7f8b4e2241c2483697b971a604252807689b9dd69bfe3a98852e19a5b89d506b000b4187a1285df8 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash@npm:^4.18.1": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + +"longest-streak@npm:^3.0.0": + version: 3.1.0 + resolution: "longest-streak@npm:3.1.0" + checksum: 10c0/7c2f02d0454b52834d1bcedef79c557bd295ee71fdabb02d041ff3aa9da48a90b5df7c0409156dedbc4df9b65da18742652aaea4759d6ece01f08971af6a7eaa + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"lower-case@npm:^2.0.2": + version: 2.0.2 + resolution: "lower-case@npm:2.0.2" + dependencies: + tslib: "npm:^2.0.3" + checksum: 10c0/3d925e090315cf7dc1caa358e0477e186ffa23947740e4314a7429b6e62d72742e0bbe7536a5ae56d19d7618ce998aba05caca53c2902bd5742fdca5fc57fd7b + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"luxon@npm:^3.7.2": + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10c0/ed8f0f637826c08c343a29dd478b00628be93bba6f068417b1d8896b61cb61c6deacbe1df1e057dbd9298334044afa150f9aaabbeb3181418ac8520acfdc2ae2 + languageName: node + linkType: hard + +"lz-string@npm:^1.5.0": + version: 1.5.0 + resolution: "lz-string@npm:1.5.0" + bin: + lz-string: bin/bin.js + checksum: 10c0/36128e4de34791838abe979b19927c26e67201ca5acf00880377af7d765b38d1c60847e01c5ec61b1a260c48029084ab3893a3925fd6e48a04011364b089991b + languageName: node + linkType: hard + +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-error@npm:^1.1.1, make-error@npm:^1.3.6": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f + languageName: node + linkType: hard + +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: "npm:1.0.5" + checksum: 10c0/b0e6e599780ce6bab49cc413eba822f7d1f0dfebd1c103eaa3785c59e43e22c59018323cf9e1708f0ef5329e94a745d163fcbb6bff8e4c6742f9be9e86f3500c + languageName: node + linkType: hard + +"map-limit@npm:0.0.1": + version: 0.0.1 + resolution: "map-limit@npm:0.0.1" + dependencies: + once: "npm:~1.3.0" + checksum: 10c0/b83e5c24b07596f65ce3a09592932ae1b9c91658587d339e9700121bae79c212d91fe60dde7f2ef976309522dae50b04858b8c06f30b770303900fdbee37239c + languageName: node + linkType: hard + +"maplibre-gl@npm:^4.7.1": + version: 4.7.1 + resolution: "maplibre-gl@npm:4.7.1" + dependencies: + "@mapbox/geojson-rewind": "npm:^0.5.2" + "@mapbox/jsonlint-lines-primitives": "npm:^2.0.2" + "@mapbox/point-geometry": "npm:^0.1.0" + "@mapbox/tiny-sdf": "npm:^2.0.6" + "@mapbox/unitbezier": "npm:^0.0.1" + "@mapbox/vector-tile": "npm:^1.3.1" + "@mapbox/whoots-js": "npm:^3.1.0" + "@maplibre/maplibre-gl-style-spec": "npm:^20.3.1" + "@types/geojson": "npm:^7946.0.14" + "@types/geojson-vt": "npm:3.2.5" + "@types/mapbox__point-geometry": "npm:^0.1.4" + "@types/mapbox__vector-tile": "npm:^1.3.4" + "@types/pbf": "npm:^3.0.5" + "@types/supercluster": "npm:^7.1.3" + earcut: "npm:^3.0.0" + geojson-vt: "npm:^4.0.2" + gl-matrix: "npm:^3.4.3" + global-prefix: "npm:^4.0.0" + kdbush: "npm:^4.0.2" + murmurhash-js: "npm:^1.0.0" + pbf: "npm:^3.3.0" + potpack: "npm:^2.0.0" + quickselect: "npm:^3.0.0" + supercluster: "npm:^8.0.1" + tinyqueue: "npm:^3.0.0" + vt-pbf: "npm:^3.1.3" + checksum: 10c0/dad2da8474af68647f882d5c4ff6df1c9afbe49f6d06c4594e4a55df0a34674f64d347b10b7af2869ac62fe837af6a56caa7617d5602724185e4df674be67b51 + languageName: node + linkType: hard + +"markdown-table@npm:^3.0.0": + version: 3.0.4 + resolution: "markdown-table@npm:3.0.4" + checksum: 10c0/1257b31827629a54c24a5030a3dac952256c559174c95ce3ef89bebd6bff0cb1444b1fd667b1a1bb53307f83278111505b3e26f0c4e7b731e0060d435d2d930b + languageName: node + linkType: hard + +"marked@npm:^16.3.0": + version: 16.4.2 + resolution: "marked@npm:16.4.2" + bin: + marked: bin/marked.js + checksum: 10c0/fc6051142172454f2023f3d6b31cca92879ec8e1b96457086a54c70354c74b00e1b6543a76a1fad6d399366f52b90a848f6ffb8e1d65a5baff87f3ba9b8f1847 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"math-log2@npm:^1.0.1": + version: 1.0.1 + resolution: "math-log2@npm:1.0.1" + checksum: 10c0/6bd247d50f526d4521bceb23c8adff5f93bc60df7e073cf14ea1a45be7e53ff2d24779b3bf22f174831a33ff33a9026022bde314b6fe77ee89cd2444489ae9e3 + languageName: node + linkType: hard + +"mdast-util-find-and-replace@npm:^3.0.0": + version: 3.0.2 + resolution: "mdast-util-find-and-replace@npm:3.0.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + escape-string-regexp: "npm:^5.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/c8417a35605d567772ff5c1aa08363ff3010b0d60c8ea68c53cba09bf25492e3dd261560425c1756535f3b7107f62e7ff3857cdd8fb1e62d1b2cc2ea6e074ca2 + languageName: node + linkType: hard + +"mdast-util-from-markdown@npm:^2.0.0": + version: 2.0.2 + resolution: "mdast-util-from-markdown@npm:2.0.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark: "npm:^4.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unist-util-stringify-position: "npm:^4.0.0" + checksum: 10c0/76eb2bd2c6f7a0318087c73376b8af6d7561c1e16654e7667e640f391341096c56142618fd0ff62f6d39e5ab4895898b9789c84cd7cec2874359a437a0e1ff15 + languageName: node + linkType: hard + +"mdast-util-gfm-autolink-literal@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-gfm-autolink-literal@npm:2.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.0.0" + mdast-util-find-and-replace: "npm:^3.0.0" + micromark-util-character: "npm:^2.0.0" + checksum: 10c0/963cd22bd42aebdec7bdd0a527c9494d024d1ad0739c43dc040fee35bdfb5e29c22564330a7418a72b5eab51d47a6eff32bc0255ef3ccb5cebfe8970e91b81b6 + languageName: node + linkType: hard + +"mdast-util-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "mdast-util-gfm-footnote@npm:2.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + checksum: 10c0/8ab965ee6be3670d76ec0e95b2ba3101fc7444eec47564943ab483d96ac17d29da2a4e6146a2a288be30c21b48c4f3938a1e54b9a46fbdd321d49a5bc0077ed0 + languageName: node + linkType: hard + +"mdast-util-gfm-strikethrough@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-strikethrough@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/b053e93d62c7545019bd914271ea9e5667ad3b3b57d16dbf68e56fea39a7e19b4a345e781312714eb3d43fdd069ff7ee22a3ca7f6149dfa774554f19ce3ac056 + languageName: node + linkType: hard + +"mdast-util-gfm-table@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-table@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + markdown-table: "npm:^3.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/128af47c503a53bd1c79f20642561e54a510ad5e2db1e418d28fefaf1294ab839e6c838e341aef5d7e404f9170b9ca3d1d89605f234efafde93ee51174a6e31e + languageName: node + linkType: hard + +"mdast-util-gfm-task-list-item@npm:^2.0.0": + version: 2.0.0 + resolution: "mdast-util-gfm-task-list-item@npm:2.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/258d725288482b636c0a376c296431390c14b4f29588675297cb6580a8598ed311fc73ebc312acfca12cc8546f07a3a285a53a3b082712e2cbf5c190d677d834 + languageName: node + linkType: hard + +"mdast-util-gfm@npm:^3.0.0": + version: 3.1.0 + resolution: "mdast-util-gfm@npm:3.1.0" + dependencies: + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-gfm-autolink-literal: "npm:^2.0.0" + mdast-util-gfm-footnote: "npm:^2.0.0" + mdast-util-gfm-strikethrough: "npm:^2.0.0" + mdast-util-gfm-table: "npm:^2.0.0" + mdast-util-gfm-task-list-item: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/4bedcfb6a20e39901c8772f0d2bb2d7a64ae87a54c13cbd92eec062cf470fbb68c2ad754e149af5b30794e2de61c978ab1de1ace03c0c40f443ca9b9b8044f81 + languageName: node + linkType: hard + +"mdast-util-mdx-expression@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdx-expression@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/9a1e57940f66431f10312fa239096efa7627f375e7933b5d3162c0b5c1712a72ac87447aff2b6838d2bbd5c1311b188718cc90b33b67dc67a88550e0a6ef6183 + languageName: node + linkType: hard + +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + ccount: "npm:^2.0.0" + devlop: "npm:^1.1.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + parse-entities: "npm:^4.0.0" + stringify-entities: "npm:^4.0.0" + unist-util-stringify-position: "npm:^4.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/3acadaf3b962254f7ad2990fed4729961dc0217ca31fde9917986e880843f3ecf3392b1f22d569235cacd180d50894ad266db7af598aedca69d330d33c7ac613 + languageName: node + linkType: hard + +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" + dependencies: + "@types/estree-jsx": "npm:^1.0.0" + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + checksum: 10c0/5bda92fc154141705af2b804a534d891f28dac6273186edf1a4c5e3f045d5b01dbcac7400d27aaf91b7e76e8dce007c7b2fdf136c11ea78206ad00bdf9db46bc + languageName: node + linkType: hard + +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/bf6c31d51349aa3d74603d5e5a312f59f3f65662ed16c58017169a5fb0f84ca98578f626c5ee9e4aa3e0a81c996db8717096705521bddb4a0185f98c12c9b42f + languageName: node + linkType: hard + +"mdast-util-to-hast@npm:^13.0.0": + version: 13.2.0 + resolution: "mdast-util-to-hast@npm:13.2.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + "@ungap/structured-clone": "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + trim-lines: "npm:^3.0.0" + unist-util-position: "npm:^5.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/9ee58def9287df8350cbb6f83ced90f9c088d72d4153780ad37854f87144cadc6f27b20347073b285173b1649b0723ddf0b9c78158608a804dcacb6bda6e1816 + languageName: node + linkType: hard + +"mdast-util-to-markdown@npm:^2.0.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" + dependencies: + "@types/mdast": "npm:^4.0.0" + "@types/unist": "npm:^3.0.0" + longest-streak: "npm:^3.0.0" + mdast-util-phrasing: "npm:^4.0.0" + mdast-util-to-string: "npm:^4.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-decode-string: "npm:^2.0.0" + unist-util-visit: "npm:^5.0.0" + zwitch: "npm:^2.0.0" + checksum: 10c0/4649722a6099f12e797bd8d6469b2b43b44e526b5182862d9c7766a3431caad2c0112929c538a972f214e63c015395e5d3f54bd81d9ac1b16e6d8baaf582f749 + languageName: node + linkType: hard + +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + checksum: 10c0/2d3c1af29bf3fe9c20f552ee9685af308002488f3b04b12fa66652c9718f66f41a32f8362aa2d770c3ff464c034860b41715902ada2306bb0a055146cef064d7 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb + languageName: node + linkType: hard + +"mermaid@npm:^11.14.0": + version: 11.14.0 + resolution: "mermaid@npm:11.14.0" + dependencies: + "@braintree/sanitize-url": "npm:^7.1.1" + "@iconify/utils": "npm:^3.0.2" + "@mermaid-js/parser": "npm:^1.1.0" + "@types/d3": "npm:^7.4.3" + "@upsetjs/venn.js": "npm:^2.0.0" + cytoscape: "npm:^3.33.1" + cytoscape-cose-bilkent: "npm:^4.1.0" + cytoscape-fcose: "npm:^2.2.0" + d3: "npm:^7.9.0" + d3-sankey: "npm:^0.12.3" + dagre-d3-es: "npm:7.0.14" + dayjs: "npm:^1.11.19" + dompurify: "npm:^3.3.1" + katex: "npm:^0.16.25" + khroma: "npm:^2.1.0" + lodash-es: "npm:^4.17.23" + marked: "npm:^16.3.0" + roughjs: "npm:^4.6.6" + stylis: "npm:^4.3.6" + ts-dedent: "npm:^2.2.0" + uuid: "npm:^11.1.0" + checksum: 10c0/2075b72d4496418ec28aa7e7d8d1b4ddcd408209940fad1efa3dc5fe98b465f38d7b5d998dc0ba3c56de639df5cc73c6395e04a9e7b18a451e2633b57a74c019 + languageName: node + linkType: hard + +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-factory-destination: "npm:^2.0.0" + micromark-factory-label: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-factory-title: "npm:^2.0.0" + micromark-factory-whitespace: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-html-tag-name: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bd4a794fdc9e88dbdf59eaf1c507ddf26e5f7ddf4e52566c72239c0f1b66adbcd219ba2cd42350debbe24471434d5f5e50099d2b3f4e5762ca222ba8e5b549ee + languageName: node + linkType: hard + +"micromark-extension-gfm-autolink-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-autolink-literal@npm:2.1.0" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/84e6fbb84ea7c161dfa179665dc90d51116de4c28f3e958260c0423e5a745372b7dcbc87d3cde98213b532e6812f847eef5ae561c9397d7f7da1e59872ef3efe + languageName: node + linkType: hard + +"micromark-extension-gfm-footnote@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-footnote@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d172e4218968b7371b9321af5cde8c77423f73b233b2b0fcf3ff6fd6f61d2e0d52c49123a9b7910612478bf1f0d5e88c75a3990dd68f70f3933fe812b9f77edc + languageName: node + linkType: hard + +"micromark-extension-gfm-strikethrough@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-strikethrough@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-classify-character: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/ef4f248b865bdda71303b494671b7487808a340b25552b11ca6814dff3fcfaab9be8d294643060bbdb50f79313e4a686ab18b99cbe4d3ee8a4170fcd134234fb + languageName: node + linkType: hard + +"micromark-extension-gfm-table@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-extension-gfm-table@npm:2.1.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/04bc00e19b435fa0add62cd029d8b7eb6137522f77832186b1d5ef34544a9bd030c9cf85e92ddfcc5c31f6f0a58a43d4b96dba4fc21316037c734630ee12c912 + languageName: node + linkType: hard + +"micromark-extension-gfm-tagfilter@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-gfm-tagfilter@npm:2.0.0" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/995558843fff137ae4e46aecb878d8a4691cdf23527dcf1e2f0157d66786be9f7bea0109c52a8ef70e68e3f930af811828ba912239438e31a9cfb9981f44d34d + languageName: node + linkType: hard + +"micromark-extension-gfm-task-list-item@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-extension-gfm-task-list-item@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/78aa537d929e9309f076ba41e5edc99f78d6decd754b6734519ccbbfca8abd52e1c62df68d41a6ae64d2a3fc1646cea955893c79680b0b4385ced4c52296181f + languageName: node + linkType: hard + +"micromark-extension-gfm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-gfm@npm:3.0.0" + dependencies: + micromark-extension-gfm-autolink-literal: "npm:^2.0.0" + micromark-extension-gfm-footnote: "npm:^2.0.0" + micromark-extension-gfm-strikethrough: "npm:^2.0.0" + micromark-extension-gfm-table: "npm:^2.0.0" + micromark-extension-gfm-tagfilter: "npm:^2.0.0" + micromark-extension-gfm-task-list-item: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/970e28df6ebdd7c7249f52a0dda56e0566fbfa9ae56c8eeeb2445d77b6b89d44096880cd57a1c01e7821b1f4e31009109fbaca4e89731bff7b83b8519690e5d9 + languageName: node + linkType: hard + +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bbafcf869cee5bf511161354cb87d61c142592fbecea051000ff116068dc85216e6d48519d147890b9ea5d7e2864a6341c0c09d9948c203bff624a80a476023c + languageName: node + linkType: hard + +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/0137716b4ecb428114165505e94a2f18855c8bbea21b07a8b5ce514b32a595ed789d2b967125718fc44c4197ceaa48f6609d58807a68e778138d2e6b91b824e8 + languageName: node + linkType: hard + +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f9ed43f1c0652d8d898de0ac2be3f77f776fffe7dd96bdbba1e02d7ce33d3853c6ff5daa52568fc4fa32cdf3a62d86b85ead9b9189f7211e1d69ff2163c450fb + languageName: node + linkType: hard + +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/e72fad8d6e88823514916890099a5af20b6a9178ccf78e7e5e05f4de99bb8797acb756257d7a3a57a53854cb0086bf8aab15b1a9e9db8982500dd2c9ff5948b6 + languageName: node + linkType: hard + +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" + dependencies: + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/20a1ec58698f24b766510a309b23a10175034fcf1551eaa9da3adcbed3e00cd53d1ebe5f030cf873f76a1cec3c34eb8c50cc227be3344caa9ed25d56cf611224 + languageName: node + linkType: hard + +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/d3fe7a5e2c4060fc2a076f9ce699c82a2e87190a3946e1e5eea77f563869b504961f5668d9c9c014724db28ac32fa909070ea8b30c3a39bd0483cc6c04cc76a1 + languageName: node + linkType: hard + +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/b68c0c16fe8106949537bdcfe1be9cf36c0ccd3bc54c4007003cb0984c3750b6cdd0fd77d03f269a3382b85b0de58bde4f6eedbe7ecdf7244759112289b1ab56 + languageName: node + linkType: hard + +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/8a02e59304005c475c332f581697e92e8c585bcd45d5d225a66c1c1b14ab5a8062705188c2ccec33cc998d33502514121478b2091feddbc751887fc9c290ed08 + languageName: node + linkType: hard + +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" + dependencies: + micromark-util-chunked: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/f15e282af24c8372cbb10b9b0b3e2c0aa681fea0ca323a44d6bc537dc1d9382c819c3689f14eaa000118f5a163245358ce6276b2cda9a84439cdb221f5d86ae7 + languageName: node + linkType: hard + +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/9c8a9f2c790e5593ffe513901c3a110e9ec8882a08f466da014112a25e5059b51551ca0aeb7ff494657d86eceb2f02ee556c6558b8d66aadc61eae4a240da0df + languageName: node + linkType: hard + +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/f24d75b2e5310be6e7b6dee532e0d17d3bf46996841d6295f2a9c87a2046fff4ab603c52ab9d7a7a6430a8b787b1574ae895849c603d262d1b22eef71736b5cb + languageName: node + linkType: hard + +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: 10c0/b2b29f901093845da8a1bf997ea8b7f5e061ffdba85070dfe14b0197c48fda64ffcf82bfe53c90cf9dc185e69eef8c5d41cae3ba918b96bc279326921b59008a + languageName: node + linkType: hard + +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: 10c0/ae80444db786fde908e9295f19a27a4aa304171852c77414516418650097b8afb401961c9edb09d677b06e97e8370cfa65638dde8438ebd41d60c0a8678b85b9 + languageName: node + linkType: hard + +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" + dependencies: + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/5299265fa360769fc499a89f40142f10a9d4a5c3dd8e6eac8a8ef3c2e4a6570e4c009cf75ea46dce5ee31c01f25587bde2f4a5cc0a935584ae86dd857f2babbd + languageName: node + linkType: hard + +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" + dependencies: + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bb6ca28764696bb479dc44a2d5b5fe003e7177aeae1d6b0d43f24cc223bab90234092d9c3ce4a4d2b8df095ccfd820537b10eb96bb7044d635f385d65a4c984a + languageName: node + linkType: hard + +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" + dependencies: + micromark-util-character: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + checksum: 10c0/60e92166e1870fd4f1961468c2651013ff760617342918e0e0c3c4e872433aa2e60c1e5a672bfe5d89dc98f742d6b33897585cf86ae002cda23e905a3c02527c + languageName: node + linkType: hard + +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" + dependencies: + devlop: "npm:^1.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/bee69eece4393308e657c293ba80d92ebcb637e5f55e21dcf9c3fa732b91a8eda8ac248d76ff375e675175bfadeae4712e5158ef97eef1111789da1ce7ab5067 + languageName: node + linkType: hard + +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: 10c0/f2d1b207771e573232436618e78c5e46cd4b5c560dd4a6d63863d58018abbf49cb96ec69f7007471e51434c60de3c9268ef2bf46852f26ff4aacd10f9da16fe9 + languageName: node + linkType: hard + +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 10c0/c8c15b96c858db781c4393f55feec10004bf7df95487636c9a9f7209e51002a5cca6a047c5d2a5dc669ff92da20e57aaa881e81a268d9ccadb647f9dce305298 + languageName: node + linkType: hard + +"micromark@npm:^4.0.0": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + devlop: "npm:^1.0.0" + micromark-core-commonmark: "npm:^2.0.0" + micromark-factory-space: "npm:^2.0.0" + micromark-util-character: "npm:^2.0.0" + micromark-util-chunked: "npm:^2.0.0" + micromark-util-combine-extensions: "npm:^2.0.0" + micromark-util-decode-numeric-character-reference: "npm:^2.0.0" + micromark-util-encode: "npm:^2.0.0" + micromark-util-normalize-identifier: "npm:^2.0.0" + micromark-util-resolve-all: "npm:^2.0.0" + micromark-util-sanitize-uri: "npm:^2.0.0" + micromark-util-subtokenize: "npm:^2.0.0" + micromark-util-symbol: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + checksum: 10c0/07462287254219d6eda6eac8a3cebaff2994e0575499e7088027b825105e096e4f51e466b14b2a81b71933a3b6c48ee069049d87bc2c2127eee50d9cc69e8af6 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.0, micromatch@npm:^4.0.4, micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-db@npm:>= 1.43.0 < 2": + version: 1.53.0 + resolution: "mime-db@npm:1.53.0" + checksum: 10c0/1dcc37ba8ed5d1c179f5c6f0837e8db19371d5f2ea3690c3c2f3fa8c3858f976851d3460b172b4dee78ebd606762cbb407aa398545fbacd539e519f858cd7bf4 + languageName: node + linkType: hard + +"mime-db@npm:~1.33.0": + version: 1.33.0 + resolution: "mime-db@npm:1.33.0" + checksum: 10c0/79172ce5468c8503b49dddfdddc18d3f5fe2599f9b5fe1bc321a8cbee14c96730fc6db22f907b23701b05b2936f865795f62ec3a78a7f3c8cb2450bb68c6763e + languageName: node + linkType: hard + +"mime-types@npm:2.1.18": + version: 2.1.18 + resolution: "mime-types@npm:2.1.18" + dependencies: + mime-db: "npm:~1.33.0" + checksum: 10c0/a96a8d12f4bb98bc7bfac6a8ccbd045f40368fc1030d9366050c3613825d3715d1c1f393e10a75a885d2cdc1a26cd6d5e11f3a2a0d5c4d361f00242139430a0f + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"minimatch@npm:3.1.5": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/2ecbdc0d33f07bddb0315a8b5afbcb761307a8778b48f0b312418ccbced99f104a2d17d8aca7573433c70e8ccd1c56823a441897a45e384ea76ef401a26ace70 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"mlly@npm:^1.7.4": + version: 1.7.4 + resolution: "mlly@npm:1.7.4" + dependencies: + acorn: "npm:^8.14.0" + pathe: "npm:^2.0.1" + pkg-types: "npm:^1.3.0" + ufo: "npm:^1.5.4" + checksum: 10c0/69e738218a13d6365caf930e0ab4e2b848b84eec261597df9788cefb9930f3e40667be9cb58a4718834ba5f97a6efeef31d3b5a95f4388143fd4e0d0deff72ff + languageName: node + linkType: hard + +"mlly@npm:^1.8.0": + version: 1.8.1 + resolution: "mlly@npm:1.8.1" + dependencies: + acorn: "npm:^8.16.0" + pathe: "npm:^2.0.3" + pkg-types: "npm:^1.3.1" + ufo: "npm:^1.6.3" + checksum: 10c0/664fa66726d3884eebf80ff7d13f7ca7f4a39f2e822d184e812f02fb70e9cc788d4182fa65dbcbe9a28653a442645c552ede487c0f58db61e2226857eb5033f7 + languageName: node + linkType: hard + +"mouse-change@npm:^1.4.0": + version: 1.4.0 + resolution: "mouse-change@npm:1.4.0" + dependencies: + mouse-event: "npm:^1.0.0" + checksum: 10c0/e500b88fe23cceaee41d2e5120b91776b983767303ec307b113e88590b414638408dfc692e31a1b64f7bddc211e5b6c74fc9a3af8067ae7ca52202c7b42f2926 + languageName: node + linkType: hard + +"mouse-event-offset@npm:^3.0.2": + version: 3.0.2 + resolution: "mouse-event-offset@npm:3.0.2" + checksum: 10c0/999a9f85082258fbd5a0478f54dbe4e6f064c13dae486e6fb39e5ebf73d81ee339f919c9ff7eb14819b6fd0e0f440f77506115e783582889f05549b2d9a1d2ef + languageName: node + linkType: hard + +"mouse-event@npm:^1.0.0": + version: 1.0.5 + resolution: "mouse-event@npm:1.0.5" + checksum: 10c0/b8da3570537a08279ae19f5f4417b7814de0271c89ea4d5746d97f3de50abd62dd4499c0fca6c5ca2af7f8ddb79986493dfd2082669aab87783558adf2d56761 + languageName: node + linkType: hard + +"mouse-wheel@npm:^1.2.0": + version: 1.2.0 + resolution: "mouse-wheel@npm:1.2.0" + dependencies: + right-now: "npm:^1.0.0" + signum: "npm:^1.0.0" + to-px: "npm:^1.0.1" + checksum: 10c0/a5710b0fa46d7465ad22146b5c43ab4475567eaee7d697936c6be751e05dad3e3433a069735bcdcd09b44de9f1cfd2223eb3e28a7a71e83ecfc3fdbeb3f064d7 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: 10c0/f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"murmurhash-js@npm:^1.0.0": + version: 1.0.0 + resolution: "murmurhash-js@npm:1.0.0" + checksum: 10c0/f8569e16db0ba6f953bf88286e97cf737f1efe97b224e537c9308566ab963a067c7eca5b636fb473d6413c4cc3b79690b78ff7ab0f290e75db91c6fde0df92b4 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"native-promise-only@npm:^0.8.1": + version: 0.8.1 + resolution: "native-promise-only@npm:0.8.1" + checksum: 10c0/c1b41128ca9806818e12d0b84f7c71e88b1fa00f0f4857767d96206dbdd0af6755305103f55c583b045533185ffca863b0c34116ade507ff7ba2e417e076a5ac + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"needle@npm:^2.5.2": + version: 2.9.1 + resolution: "needle@npm:2.9.1" + dependencies: + debug: "npm:^3.2.6" + iconv-lite: "npm:^0.4.4" + sax: "npm:^1.2.4" + bin: + needle: ./bin/needle + checksum: 10c0/65a7eaeaf4ca3410de492957474592af9838e02875273d9232ff6cff331393c58a95e48c592bd9a05f575e5bb9b08543d6cfd19eb96595dbd3d7da2c5fe1accb + languageName: node + linkType: hard + +"negotiator@npm:~0.6.4": + version: 0.6.4 + resolution: "negotiator@npm:0.6.4" + checksum: 10c0/3e677139c7fb7628a6f36335bf11a885a62c21d5390204590a1a214a5631fcbe5ea74ef6a610b60afe84b4d975cbe0566a23f20ee17c77c73e74b80032108dea + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10c0/c2f5a604a54a8ec5438a342e1f356dff4bc33ccccdb6dc668d94fe8e5eccfc9d2c2eea6064b0967a767ba63b33763f51ccf2cd2441b461a7322656c1f06b3f5d + languageName: node + linkType: hard + +"next-tick@npm:^1.1.0": + version: 1.1.0 + resolution: "next-tick@npm:1.1.0" + checksum: 10c0/3ba80dd805fcb336b4f52e010992f3e6175869c8d88bf4ff0a81d5d66e6049f89993463b28211613e58a6b7fe93ff5ccbba0da18d4fa574b96289e8f0b577f28 + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10c0/95568c1b73e1d0d4069a3e3061a2102d854513d37bcfda73300015b7ba4868d3b27c198d1dbbd8ebdef4112fc2ed9e895d4a0f2e1cce0bd334f2a1346dc9205f + languageName: node + linkType: hard + +"no-case@npm:^3.0.4": + version: 3.0.4 + resolution: "no-case@npm:3.0.4" + dependencies: + lower-case: "npm:^2.0.2" + tslib: "npm:^2.0.3" + checksum: 10c0/8ef545f0b3f8677c848f86ecbd42ca0ff3cd9dd71c158527b344c69ba14710d816d8489c746b6ca225e7b615108938a0bda0a54706f8c255933703ac1cf8e703 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.3.0 + resolution: "node-gyp@npm:12.3.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^6.25.0" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/9d9032b405cbe42f72a105259d9eb679376470c102df4a2dbaa51e07d59bf741dcffb85897087ea9d8318b9cabb824a8978af51508ae142f0239ae1e6a3c2329 + languageName: node + linkType: hard + +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: 10c0/a6a4d8369e2f2720e9c645255ffde909c0fbd41c92ea92a5607fc17055955daac99c1ff589d421eee12a0d24e99f7bfc2aabfeb1a4c14742f6c099a51863f31a + languageName: node + linkType: hard + +"node-releases@npm:^2.0.19": + version: 2.0.19 + resolution: "node-releases@npm:2.0.19" + checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10c0/e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 + languageName: node + linkType: hard + +"normalize-svg-path@npm:^1.0.0": + version: 1.1.0 + resolution: "normalize-svg-path@npm:1.1.0" + dependencies: + svg-arc-to-cubic-bezier: "npm:^3.0.0" + checksum: 10c0/2e24e1d0a9ca7f172cec161d9c14fef616c57040664d82f6077d97c42e6e8a113f288dc0cb55e19fc4873baa0c5ddc7857d24d4a1b6df594924451d1adbb1b41 + languageName: node + linkType: hard + +"normalize-svg-path@npm:~0.1.0": + version: 0.1.0 + resolution: "normalize-svg-path@npm:0.1.0" + checksum: 10c0/a693092f83e1f1836d0dac2d36c9d13455552987f31e42a91e1d96a94c2aaca6c4e3fea3348acb9b78380ec6f5fc7573176438f614c9c359c384ce03a1d7856d + languageName: node + linkType: hard + +"npm-run-path@npm:^2.0.0": + version: 2.0.2 + resolution: "npm-run-path@npm:2.0.2" + dependencies: + path-key: "npm:^2.0.0" + checksum: 10c0/95549a477886f48346568c97b08c4fda9cdbf7ce8a4fbc2213f36896d0d19249e32d68d7451bdcbca8041b5fba04a6b2c4a618beaf19849505c05b700740f1de + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"npm-run-path@npm:^6.0.0": + version: 6.0.0 + resolution: "npm-run-path@npm:6.0.0" + dependencies: + path-key: "npm:^4.0.0" + unicorn-magic: "npm:^0.3.0" + checksum: 10c0/b223c8a0dcd608abf95363ea5c3c0ccc3cd877daf0102eaf1b0f2390d6858d8337fbb7c443af2403b067a7d2c116d10691ecd22ab3c5273c44da1ff8d07753bd + languageName: node + linkType: hard + +"number-is-integer@npm:^1.0.1": + version: 1.0.1 + resolution: "number-is-integer@npm:1.0.1" + dependencies: + is-finite: "npm:^1.0.1" + checksum: 10c0/8108530e1d6321f1f62855343d7cb02235b76b1127bc2665420d65a8ce0804c3b33441228d40a320bc870ddb6feeece5c9486bae883868eb771b03c3597ba939 + languageName: node + linkType: hard + +"nwsapi@npm:^2.2.2": + version: 2.2.16 + resolution: "nwsapi@npm:2.2.16" + checksum: 10c0/0aa0637f4d51043d0183d994e08336bae996b03b42984381bf09ebdf3ff4909c018eda6b2a8aba0a08f3ea8303db8a0dad0608b38dc0bff15fd87017286ae21a + languageName: node + linkType: hard + +"object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + +"on-headers@npm:~1.1.0": + version: 1.1.0 + resolution: "on-headers@npm:1.1.0" + checksum: 10c0/2c3b6b0d68ec9adbd561dc2d61c9b14da8ac03d8a2f0fd9e97bdf0600c887d5d97f664ff3be6876cf40cda6e3c587d73a4745e10b426ac50c7664fc5a0dfc0a1 + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10c0/5d48aca287dfefabd756621c5dfce5c91a549a93e9fdb7b8246bc4c4790aa2ec17b34a260530474635147aeb631a2dcc8b32c613df0675f96041cbb8244517d0 + languageName: node + linkType: hard + +"once@npm:~1.3.0": + version: 1.3.3 + resolution: "once@npm:1.3.3" + dependencies: + wrappy: "npm:1" + checksum: 10c0/bb9c0fa8f6420b89fea4e0fc80aac175f025358cd1374a8e7afb92672f58473684f45a784e18f147d07cf87e629cc277f63f35c48fdfdced662edd18779c5876 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^3.1.0": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + +"package-manager-detector@npm:^1.3.0": + version: 1.6.0 + resolution: "package-manager-detector@npm:1.6.0" + checksum: 10c0/6419d0b840be64fd45bcdcb7a19f09b81b65456d5e7f7a3daac305a4c90643052122f6ac0308afe548ffee75e36148532a2002ea9d292754f1e385aa2e1ea03b + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parenthesis@npm:^3.1.5": + version: 3.1.8 + resolution: "parenthesis@npm:3.1.8" + checksum: 10c0/1c0d5dd8b238b7c703db5ff1ca35f41952a7b2597c2e97cd0d32ab345179062611bc4f8a849787ed7f23508e91140d74fea7c0050b6978c56499f707ff54c99c + languageName: node + linkType: hard + +"parse-entities@npm:^4.0.0": + version: 4.0.2 + resolution: "parse-entities@npm:4.0.2" + dependencies: + "@types/unist": "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + character-reference-invalid: "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + is-alphanumerical: "npm:^2.0.0" + is-decimal: "npm:^2.0.0" + is-hexadecimal: "npm:^2.0.0" + checksum: 10c0/a13906b1151750b78ed83d386294066daf5fb559e08c5af9591b2d98cc209123103016a01df776f65f8219ad26652d6d6b210d0974d452049cddfc53a8916c34 + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"parse-rect@npm:^1.2.0": + version: 1.2.0 + resolution: "parse-rect@npm:1.2.0" + dependencies: + pick-by-alias: "npm:^1.2.0" + checksum: 10c0/70af64ef6b34a2d460ba54cb09c9f9159536c93278aae2d7878d0557e71d8b648406203a74db3955e68ce4c47f6c10b8d24288985628dbc2877d4390fba1d27c + languageName: node + linkType: hard + +"parse-svg-path@npm:^0.1.2": + version: 0.1.2 + resolution: "parse-svg-path@npm:0.1.2" + checksum: 10c0/005af72d535f47bbe4673108e51d657b81cb19dcc4f10ab425dd9f824066ef300ddbc83640607ef318f853a9fce7065114705531dcf88fb87fe58c287b562f2b + languageName: node + linkType: hard + +"parse-unit@npm:^1.0.1": + version: 1.0.1 + resolution: "parse-unit@npm:1.0.1" + checksum: 10c0/a7e63091ddccfd920ab1b85899ecb6cb0af8069fe36c5f8f6fa80eeb262a52686ab94d21ff02cde821570cb0d9c95e2f454672959a0f39df52ee5d3c95276022 + languageName: node + linkType: hard + +"parse5@npm:^7.0.0, parse5@npm:^7.1.1": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" + dependencies: + entities: "npm:^4.5.0" + checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 + languageName: node + linkType: hard + +"path-data-parser@npm:0.1.0, path-data-parser@npm:^0.1.0": + version: 0.1.0 + resolution: "path-data-parser@npm:0.1.0" + checksum: 10c0/ba22d54669a8bc4a3df27431fe667900685585d1196085b803d0aa4066b83e709bbf2be7c1d2b56e706b49cc698231d55947c22abbfc4843ca424bbf8c985745 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10c0/127da03c82172a2a50099cddbf02510c1791fc2cc5f7713ddb613a56838db1e8168b121a920079d052e0936c23005562059756d653b7c544c53185efe53be078 + languageName: node + linkType: hard + +"path-is-inside@npm:1.0.2": + version: 1.0.2 + resolution: "path-is-inside@npm:1.0.2" + checksum: 10c0/7fdd4b41672c70461cce734fc222b33e7b447fa489c7c4377c95e7e6852d83d69741f307d88ec0cc3b385b41cb4accc6efac3c7c511cd18512e95424f5fa980c + languageName: node + linkType: hard + +"path-key@npm:^2.0.0, path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10c0/dd2044f029a8e58ac31d2bf34c34b93c3095c1481942960e84dd2faa95bbb71b9b762a106aead0646695330936414b31ca0bd862bf488a937ad17c8c5d73b32b + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-key@npm:^4.0.0": + version: 4.0.0 + resolution: "path-key@npm:4.0.0" + checksum: 10c0/794efeef32863a65ac312f3c0b0a99f921f3e827ff63afa5cb09a377e202c262b671f7b3832a4e64731003fa94af0263713962d317b9887bd1e0c48a342efba3 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-to-regexp@npm:3.3.0": + version: 3.3.0 + resolution: "path-to-regexp@npm:3.3.0" + checksum: 10c0/ffa0ebe7088d38d435a8d08b0fe6e8c93ceb2a81a65d4dd1d9a538f52e09d5e3474ed5f553cb3b180d894b0caa10698a68737ab599fd1e56b4663d1a64c9f77b + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pathe@npm:^2.0.1": + version: 2.0.2 + resolution: "pathe@npm:2.0.2" + checksum: 10c0/21fce96ca9cebf037b075de8e5cc4ac6aa1009bce57946a72695f47ded84cf4b29f03bed721ea0f6e39b69eb1a0620bcee1f72eca46086765214a2965399b83a + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pbf@npm:^3.2.1, pbf@npm:^3.3.0": + version: 3.3.0 + resolution: "pbf@npm:3.3.0" + dependencies: + ieee754: "npm:^1.1.12" + resolve-protobuf-schema: "npm:^2.1.0" + bin: + pbf: bin/pbf + checksum: 10c0/79e5dc59a9391789de84b0a6d713fad0dd1e5ce6eb721536af8c9ec49feae04fdebab9f077b760bba858e615a95ac714a7d248ecb43736e904bb8396885e16d6 + languageName: node + linkType: hard + +"performance-now@npm:^2.1.0": + version: 2.1.0 + resolution: "performance-now@npm:2.1.0" + checksum: 10c0/22c54de06f269e29f640e0e075207af57de5052a3d15e360c09b9a8663f393f6f45902006c1e71aa8a5a1cdfb1a47fe268826f8496d6425c362f00f5bc3e85d9 + languageName: node + linkType: hard + +"pick-by-alias@npm:^1.2.0": + version: 1.2.0 + resolution: "pick-by-alias@npm:1.2.0" + checksum: 10c0/2336088c95a04f50b088986394e35d2d0b63a95d85ffec547dcdb0fe1ccd710ab36569607fcf72d8654bbbf49e1f983a25f2a7c57e8ff2d587cf86070fc669b6 + languageName: node + linkType: hard + +"picocolors@npm:1.1.1, picocolors@npm:^1.0.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.4": + version: 4.0.4 + resolution: "picomatch@npm:4.0.4" + checksum: 10c0/e2c6023372cc7b5764719a5ffb9da0f8e781212fa7ca4bd0562db929df8e117460f00dff3cb7509dacfc06b86de924b247f504d0ce1806a37fac4633081466b0 + languageName: node + linkType: hard + +"pirates@npm:^4.0.4": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 10c0/00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: "npm:^4.0.0" + checksum: 10c0/c56bda7769e04907a88423feb320babaed0711af8c436ce3e56763ab1021ba107c7b0cafb11cde7529f669cfc22bffcaebffb573645cbd63842ea9fb17cd7728 + languageName: node + linkType: hard + +"pkg-types@npm:^1.3.0, pkg-types@npm:^1.3.1": + version: 1.3.1 + resolution: "pkg-types@npm:1.3.1" + dependencies: + confbox: "npm:^0.1.8" + mlly: "npm:^1.7.4" + pathe: "npm:^2.0.1" + checksum: 10c0/19e6cb8b66dcc66c89f2344aecfa47f2431c988cfa3366bdfdcfb1dd6695f87dcce37fbd90fe9d1605e2f4440b77f391e83c23255347c35cf84e7fd774d7fcea + languageName: node + linkType: hard + +"plotly.js@npm:^3.5.0": + version: 3.5.0 + resolution: "plotly.js@npm:3.5.0" + dependencies: + "@plotly/d3": "npm:3.8.2" + "@plotly/d3-sankey": "npm:0.7.2" + "@plotly/d3-sankey-circular": "npm:0.33.1" + "@plotly/mapbox-gl": "npm:1.13.4" + "@plotly/regl": "npm:^2.1.2" + "@turf/area": "npm:^7.1.0" + "@turf/bbox": "npm:^7.1.0" + "@turf/centroid": "npm:^7.1.0" + base64-arraybuffer: "npm:^1.0.2" + canvas-fit: "npm:^1.5.0" + color-alpha: "npm:1.0.4" + color-normalize: "npm:1.5.0" + color-parse: "npm:2.0.0" + color-rgba: "npm:3.0.0" + country-regex: "npm:^1.1.0" + d3-force: "npm:^1.2.1" + d3-format: "npm:^1.4.5" + d3-geo: "npm:^1.12.1" + d3-geo-projection: "npm:^2.9.0" + d3-hierarchy: "npm:^1.1.9" + d3-interpolate: "npm:^3.0.1" + d3-time: "npm:^1.1.0" + d3-time-format: "npm:^2.2.3" + fast-isnumeric: "npm:^1.1.4" + gl-mat4: "npm:^1.2.0" + gl-text: "npm:^1.4.0" + has-hover: "npm:^1.0.1" + has-passive-events: "npm:^1.0.0" + is-mobile: "npm:^4.0.0" + maplibre-gl: "npm:^4.7.1" + mouse-change: "npm:^1.4.0" + mouse-event-offset: "npm:^3.0.2" + mouse-wheel: "npm:^1.2.0" + native-promise-only: "npm:^0.8.1" + parse-svg-path: "npm:^0.1.2" + point-in-polygon: "npm:^1.1.0" + polybooljs: "npm:^1.2.2" + probe-image-size: "npm:^7.2.3" + regl-error2d: "npm:^2.0.12" + regl-line2d: "npm:^3.1.3" + regl-scatter2d: "npm:^3.3.1" + regl-splom: "npm:^1.0.14" + strongly-connected-components: "npm:^1.0.1" + superscript-text: "npm:^1.0.0" + svg-path-sdf: "npm:^1.1.3" + tinycolor2: "npm:^1.4.2" + to-px: "npm:1.0.1" + topojson-client: "npm:^3.1.0" + webgl-context: "npm:^2.2.0" + world-calendars: "npm:^1.0.4" + checksum: 10c0/5247d4be5fb39268d14b95fa3d4d278f56bbd35c01b53b71eb331208c66d936044ef42dd2e3b2574987171fc1d04bdb7cf53e5dce1a113f19ef2f0bf80aaacaa + languageName: node + linkType: hard + +"point-in-polygon@npm:^1.1.0": + version: 1.1.0 + resolution: "point-in-polygon@npm:1.1.0" + checksum: 10c0/de00419585ee25555d97585b7a23eeb2464a87ef29404264bee55654ca2ecab5a5a99d33e689c07d045faf80091e838f44a1fd130bdd6134493df53114947343 + languageName: node + linkType: hard + +"points-on-curve@npm:0.2.0, points-on-curve@npm:^0.2.0": + version: 0.2.0 + resolution: "points-on-curve@npm:0.2.0" + checksum: 10c0/f0d92343fcc2ad1f48334633e580574c1e0e28038a756133e171e537f270d6d64203feada5ee556e36f448a1b46e0306dee07b30f589f4e3ad720f6ee38ef48c + languageName: node + linkType: hard + +"points-on-path@npm:^0.2.1": + version: 0.2.1 + resolution: "points-on-path@npm:0.2.1" + dependencies: + path-data-parser: "npm:0.1.0" + points-on-curve: "npm:0.2.0" + checksum: 10c0/a7010340f9f196976f61838e767bb7b0b7f6273ab4fb9eb37c61001fe26fbfc3fcd63c96d5e85b9a4ab579213ab366f2ddaaf60e2a9253e2b91a62db33f395ba + languageName: node + linkType: hard + +"polybooljs@npm:^1.2.2": + version: 1.2.2 + resolution: "polybooljs@npm:1.2.2" + checksum: 10c0/74f6e2d20cabde6c6d3021974e0ac54c85422f08a8c0b96add15cd97c23dcbb13509ca00086fdee2922ab7c85946a047e51e8c2f30df68bbc36dbd0c00ef2e30 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.0.2": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 10c0/f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 + languageName: node + linkType: hard + +"postcss@npm:^8.5.10": + version: 8.5.10 + resolution: "postcss@npm:8.5.10" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/c592dffa0c4873b401f01955b265538d9942f425040df5e2b8f0ad34c83773a792ea0fa5859ccc99cfb5b955b4ebff118ab7056315388dc83b107b0fa8313576 + languageName: node + linkType: hard + +"postcss@npm:^8.5.6": + version: 8.5.8 + resolution: "postcss@npm:8.5.8" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/dd918f7127ee7c60a0295bae2e72b3787892296e1d1c3c564d7a2a00c68d8df83cadc3178491259daa19ccc54804fb71ed8c937c6787e08d8bd4bedf8d17044c + languageName: node + linkType: hard + +"potpack@npm:^1.0.1": + version: 1.0.2 + resolution: "potpack@npm:1.0.2" + checksum: 10c0/670c23898a4257130858b960c2e654d3327c0f6a7e7091ff5846f213e65af8f9476320b995b8ad561a47a4d1c359c7ef347de57d22e7b02597051abb52bc85c4 + languageName: node + linkType: hard + +"potpack@npm:^2.0.0": + version: 2.0.0 + resolution: "potpack@npm:2.0.0" + checksum: 10c0/8df693484486535b95586a624c5abf3fd7bebad161b1c10c10d86562c870fe1c90703b862a19020b1d1c2f3fbd6cea1e9d699c6eca6ded4b5792873a992c81e1 + languageName: node + linkType: hard + +"pretty-format@npm:^27.0.2": + version: 27.5.1 + resolution: "pretty-format@npm:27.5.1" + dependencies: + ansi-regex: "npm:^5.0.1" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^17.0.1" + checksum: 10c0/0cbda1031aa30c659e10921fa94e0dd3f903ecbbbe7184a729ad66f2b6e7f17891e8c7d7654c458fa4ccb1a411ffb695b4f17bbcd3fe075fabe181027c4040ed + languageName: node + linkType: hard + +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10c0/edc5ff89f51916f036c62ed433506b55446ff739358de77207e63e88a28ca2894caac6e73dcb68166a606e51c8087d32d400473e6a9fdd2dbe743f46c9c0276f + languageName: node + linkType: hard + +"probe-image-size@npm:^7.2.3": + version: 7.2.3 + resolution: "probe-image-size@npm:7.2.3" + dependencies: + lodash.merge: "npm:^4.6.2" + needle: "npm:^2.5.2" + stream-parser: "npm:~0.3.1" + checksum: 10c0/bebe3b050889794565b66ea9749cb21fee4f3e99fea41a328e39f2929f2432ebb50ac974148c35c66dec5becc849b3185a7a6f39d3ff75247e8be0a2759c9627 + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"proper-lockfile@npm:^4.1.2": + version: 4.1.2 + resolution: "proper-lockfile@npm:4.1.2" + dependencies: + graceful-fs: "npm:^4.2.4" + retry: "npm:^0.12.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/2f265dbad15897a43110a02dae55105c04d356ec4ed560723dcb9f0d34bc4fb2f13f79bb930e7561be10278e2314db5aca2527d5d3dcbbdee5e6b331d1571f6d + languageName: node + linkType: hard + +"property-information@npm:^7.0.0": + version: 7.0.0 + resolution: "property-information@npm:7.0.0" + checksum: 10c0/bf443e3bbdfc154da8f4ff4c85ed97c3d21f5e5f77cce84d2fd653c6dfb974a75ad61eafbccb2b8d2285942be35d763eaa99d51e29dccc28b40917d3f018107e + languageName: node + linkType: hard + +"protocol-buffers-schema@npm:^3.3.1": + version: 3.6.0 + resolution: "protocol-buffers-schema@npm:3.6.0" + checksum: 10c0/23a08612e5cc903f917ae3b680216ccaf2d889c61daa68d224237f455182fa96fff16872ac94b1954b5dd26fc7e8ce7e9360c54d54ea26218d107b2f059fca37 + languageName: node + linkType: hard + +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + +"psl@npm:^1.1.33": + version: 1.15.0 + resolution: "psl@npm:1.15.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 10c0/d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.4 + resolution: "pump@npm:3.0.4" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10c0/2780e66b5471c19e3e3e1063b84f3f6a3a08367f24c5ed552f98cd5901e6ada27c7ad6495d4244f553fd03b01884a4561933064f053f47c8994d84fd352768ea + languageName: node + linkType: hard + +"punycode@npm:^2.1.1, punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 + languageName: node + linkType: hard + +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 10c0/3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 + languageName: node + linkType: hard + +"quickselect@npm:^2.0.0": + version: 2.0.0 + resolution: "quickselect@npm:2.0.0" + checksum: 10c0/6c8d591bc73beae4c1996b7b7138233a7dbbbdde29b7b6d822a02d08cd220fd27613f47d6e9635989b12e250d42ef9da3448de1ed12ad962974e207ab3c3562c + languageName: node + linkType: hard + +"quickselect@npm:^3.0.0": + version: 3.0.0 + resolution: "quickselect@npm:3.0.0" + checksum: 10c0/3a0d33b0ec06841d953accdfd735aa3d8b7922cddd12970544a2c4b0278871280d8f5ba496803600693c1e7b7b2fb57c31d2b14d99132f478888006a1be6e6b7 + languageName: node + linkType: hard + +"raf@npm:^3.4.1": + version: 3.4.1 + resolution: "raf@npm:3.4.1" + dependencies: + performance-now: "npm:^2.1.0" + checksum: 10c0/337f0853c9e6a77647b0f499beedafea5d6facfb9f2d488a624f88b03df2be72b8a0e7f9118a3ff811377d534912039a3311815700d2b6d2313f82f736f9eb6e + languageName: node + linkType: hard + +"range-parser@npm:1.2.0": + version: 1.2.0 + resolution: "range-parser@npm:1.2.0" + checksum: 10c0/c7aef4f6588eb974c475649c157f197d07437d8c6c8ff7e36280a141463fb5ab7a45918417334ebd7b665c6b8321cf31c763f7631dd5f5db9372249261b8b02a + languageName: node + linkType: hard + +"rc@npm:^1.0.1, rc@npm:^1.1.6": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15 + languageName: node + linkType: hard + +"react-aria@npm:^3.47.0": + version: 3.48.0 + resolution: "react-aria@npm:3.48.0" + dependencies: + "@internationalized/date": "npm:^3.12.1" + "@internationalized/number": "npm:^3.6.6" + "@internationalized/string": "npm:^3.2.8" + "@react-types/shared": "npm:^3.34.0" + "@swc/helpers": "npm:^0.5.0" + aria-hidden: "npm:^1.2.3" + clsx: "npm:^2.0.0" + react-stately: "npm:3.46.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/908d30941f24510dd73eb744d9b83c102949e559ae17d972b880c564c464bcf708610d8defc708b8ff85db3996bd4f42818cb6d2b9be390a42265595522547f6 + languageName: node + linkType: hard + +"react-dom@npm:^19.2.5": + version: 19.2.5 + resolution: "react-dom@npm:19.2.5" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.5 + checksum: 10c0/8067606e9f58e4c2e8cb5f09570217dbc71c4843ebcaa20ae2085912d3e3a351f17d8f7c1713313cdda7f272840c8c34ff6c860fcb840862071bceea218e0c63 + languageName: node + linkType: hard + +"react-hook-form@npm:^7.73.1": + version: 7.73.1 + resolution: "react-hook-form@npm:7.73.1" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + checksum: 10c0/42036e20863cfdadaac533fc176ab7d6d6c82ec20901c0a1a5ae1a36e015b5433eacb6b76a0217eb34f7c93ddeacb216640e2c3138a2b80fa399d7c115961fb9 + languageName: node + linkType: hard + +"react-icons@npm:5.6.0": + version: 5.6.0 + resolution: "react-icons@npm:5.6.0" + peerDependencies: + react: "*" + checksum: 10c0/addba1ebfd45cdf7f69291d0c93df64fca1271cfdb66df657abd223e3451c73356b035f50e75858627dd182b02129596c79eaaaa45d32eb52658e443a992645c + languageName: node + linkType: hard + +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^17.0.1": + version: 17.0.2 + resolution: "react-is@npm:17.0.2" + checksum: 10c0/2bdb6b93fbb1820b024b496042cce405c57e2f85e777c9aabd55f9b26d145408f9f74f5934676ffdc46f3dcff656d78413a6e43968e7b3f92eea35b3052e9053 + languageName: node + linkType: hard + +"react-is@npm:^18.0.0, react-is@npm:^18.2.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 + languageName: node + linkType: hard + +"react-markdown@npm:^10.1.0": + version: 10.1.0 + resolution: "react-markdown@npm:10.1.0" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + devlop: "npm:^1.0.0" + hast-util-to-jsx-runtime: "npm:^2.0.0" + html-url-attributes: "npm:^3.0.0" + mdast-util-to-hast: "npm:^13.0.0" + remark-parse: "npm:^11.0.0" + remark-rehype: "npm:^11.0.0" + unified: "npm:^11.0.0" + unist-util-visit: "npm:^5.0.0" + vfile: "npm:^6.0.0" + peerDependencies: + "@types/react": ">=18" + react: ">=18" + checksum: 10c0/4a5dc7d15ca6d05e9ee95318c1904f83b111a76f7588c44f50f1d54d4c97193b84e4f64c4b592057c989228238a2590306cedd0c4d398e75da49262b2b5ae1bf + languageName: node + linkType: hard + +"react-oauth2-code-pkce@npm:^1.24.0": + version: 1.24.0 + resolution: "react-oauth2-code-pkce@npm:1.24.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/82ba976fe925acd8af8940006f2c04d4b2654f6f41cefc96cba3c0367efa6ce4cf2f901a3da27a1aed635295ae74a621019719b1cd4c41e74e1fe0b685341718 + languageName: node + linkType: hard + +"react-plotly.js@npm:^2.6.0": + version: 2.6.0 + resolution: "react-plotly.js@npm:2.6.0" + dependencies: + prop-types: "npm:^15.8.1" + peerDependencies: + plotly.js: ">1.34.0" + react: ">0.13.0" + checksum: 10c0/9b5ba8e33782daa9624d79295975d08d4c7581bac41d67d497eec0a306e4d030465a1e8282fab145bc02705fe96262f6fb0d214133657c480982fc268763644a + languageName: node + linkType: hard + +"react-router-dom@npm:^7.14.2": + version: 7.14.2 + resolution: "react-router-dom@npm:7.14.2" + dependencies: + react-router: "npm:7.14.2" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/94b294e7e47247d52358415a4b76e629680485b81bbaa649138b04277580276370f0b380b84bb1ffda21c330ed8c5cb36fb77ba5e173dcaaf06b5bfc44d9ac4e + languageName: node + linkType: hard + +"react-router@npm:7.14.2": + version: 7.14.2 + resolution: "react-router@npm:7.14.2" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/83dc99158ff4c6513f2df358ca0e9920ba836672e491824ff9cf8d2218049db2859fafce147834f5c990cfe516cfdeefb5ffc3f49f917c0b30c2b22697ed96b7 + languageName: node + linkType: hard + +"react-stately@npm:3.46.0": + version: 3.46.0 + resolution: "react-stately@npm:3.46.0" + dependencies: + "@internationalized/date": "npm:^3.12.1" + "@internationalized/number": "npm:^3.6.6" + "@internationalized/string": "npm:^3.2.8" + "@react-types/shared": "npm:^3.34.0" + "@swc/helpers": "npm:^0.5.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + checksum: 10c0/380b428c1a79766401e3f2472486b1c2a5e04d3d3a27a011cf7f7ee6b9a24c56026659ebea9e08b65495fbef02bd4a98817895ff7d84db5df4fc6ce23e01cba8 + languageName: node + linkType: hard + +"react-toastify@npm:^11.1.0": + version: 11.1.0 + resolution: "react-toastify@npm:11.1.0" + dependencies: + clsx: "npm:^2.1.1" + peerDependencies: + react: ^18 || ^19 + react-dom: ^18 || ^19 + checksum: 10c0/b621b8272bb9287ec19914b773d91c2f7741eedba253f2986150716d30c82ff069ba9780a7ab4cbb511175b3aeb6e35f003aef3f04742d77df79fb8f4c1c3c98 + languageName: node + linkType: hard + +"react@npm:^19.2.5": + version: 19.2.5 + resolution: "react@npm:19.2.5" + checksum: 10c0/4b5f231dbef92886f602533c9ce3bde04d99f0e71dfb5d794c43e02726efaad0421c08688f75fc98a6d6e1dc017372e1af7abbfecdc86a79968f461675931a7a + languageName: node + linkType: hard + +"readable-stream@npm:>=1.0.33-1 <1.1.0-0": + version: 1.0.34 + resolution: "readable-stream@npm:1.0.34" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.1" + isarray: "npm:0.0.1" + string_decoder: "npm:~0.10.x" + checksum: 10c0/02272551396ed8930ddee1a088bdf0379f0f7cc47ac49ed8804e998076cb7daec9fbd2b1fd9c0490ec72e56e8bb3651abeb8080492b8e0a9c3f2158330908ed6 + languageName: node + linkType: hard + +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.2.2, readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"rechoir@npm:^0.6.2": + version: 0.6.2 + resolution: "rechoir@npm:0.6.2" + dependencies: + resolve: "npm:^1.1.6" + checksum: 10c0/22c4bb32f4934a9468468b608417194f7e3ceba9a508512125b16082c64f161915a28467562368eeb15dc16058eb5b7c13a20b9eb29ff9927d1ebb3b5aa83e84 + languageName: node + linkType: hard + +"regenerator-runtime@npm:^0.14.0": + version: 0.14.1 + resolution: "regenerator-runtime@npm:0.14.1" + checksum: 10c0/1b16eb2c4bceb1665c89de70dcb64126a22bc8eb958feef3cd68fe11ac6d2a4899b5cd1b80b0774c7c03591dc57d16631a7f69d2daa2ec98100e2f29f7ec4cc4 + languageName: node + linkType: hard + +"registry-auth-token@npm:3.3.2": + version: 3.3.2 + resolution: "registry-auth-token@npm:3.3.2" + dependencies: + rc: "npm:^1.1.6" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/934b5d504ec6d94d78672dc5e74646c52793e74a6e400c1cffc78838bbb12c5f45e3ef3edba506f3295db794d4dda76f924f2948d48fe1f8e83b6500b0ba53c5 + languageName: node + linkType: hard + +"registry-url@npm:3.1.0": + version: 3.1.0 + resolution: "registry-url@npm:3.1.0" + dependencies: + rc: "npm:^1.0.1" + checksum: 10c0/345cf9638f99d95863d92800b3f595ac312c19d6865595e499fbeb33fcda04021a0dbdafbb5e61a838a89a558bc239d78752a1f90eb68cf53fdf0d91da816a7c + languageName: node + linkType: hard + +"regl-error2d@npm:^2.0.12": + version: 2.0.12 + resolution: "regl-error2d@npm:2.0.12" + dependencies: + array-bounds: "npm:^1.0.1" + color-normalize: "npm:^1.5.0" + flatten-vertex-data: "npm:^1.0.2" + object-assign: "npm:^4.1.1" + pick-by-alias: "npm:^1.2.0" + to-float32: "npm:^1.1.0" + update-diff: "npm:^1.1.0" + checksum: 10c0/999bdbb25bf2ab81fe14d9d630db15e81aa590e8b723b8030e7757f4393ff2eb3924e52044a12125804da5fe9d89f5ed1a2bf64340aabaf4cee7ea729ae2398d + languageName: node + linkType: hard + +"regl-line2d@npm:^3.1.3": + version: 3.1.3 + resolution: "regl-line2d@npm:3.1.3" + dependencies: + array-bounds: "npm:^1.0.1" + array-find-index: "npm:^1.0.2" + array-normalize: "npm:^1.1.4" + color-normalize: "npm:^1.5.0" + earcut: "npm:^2.1.5" + es6-weak-map: "npm:^2.0.3" + flatten-vertex-data: "npm:^1.0.2" + object-assign: "npm:^4.1.1" + parse-rect: "npm:^1.2.0" + pick-by-alias: "npm:^1.2.0" + to-float32: "npm:^1.1.0" + checksum: 10c0/70673f4b362db65cf878d2008847cb5c6f08c3b0b15c6e4df896f276a6b1b5185aaed2e0248fafe8d2c203d88814f6026060b2656d532d87268151f7c011dabc + languageName: node + linkType: hard + +"regl-scatter2d@npm:^3.2.3, regl-scatter2d@npm:^3.3.1": + version: 3.3.1 + resolution: "regl-scatter2d@npm:3.3.1" + dependencies: + "@plotly/point-cluster": "npm:^3.1.9" + array-range: "npm:^1.0.1" + array-rearrange: "npm:^2.2.2" + clamp: "npm:^1.0.1" + color-id: "npm:^1.1.0" + color-normalize: "npm:^1.5.0" + color-rgba: "npm:^2.1.1" + flatten-vertex-data: "npm:^1.0.2" + glslify: "npm:^7.0.0" + is-iexplorer: "npm:^1.0.0" + object-assign: "npm:^4.1.1" + parse-rect: "npm:^1.2.0" + pick-by-alias: "npm:^1.2.0" + to-float32: "npm:^1.1.0" + update-diff: "npm:^1.1.0" + checksum: 10c0/9a7f69d2039aeb1cca125d0d101cb122234b9e4ac56e190c9f434198ecd27411d6d4b70e2f2aee5a97471060aef290b74b9cac434398166ac330c47901e99283 + languageName: node + linkType: hard + +"regl-splom@npm:^1.0.14": + version: 1.0.14 + resolution: "regl-splom@npm:1.0.14" + dependencies: + array-bounds: "npm:^1.0.1" + array-range: "npm:^1.0.1" + color-alpha: "npm:^1.0.4" + flatten-vertex-data: "npm:^1.0.2" + parse-rect: "npm:^1.2.0" + pick-by-alias: "npm:^1.2.0" + raf: "npm:^3.4.1" + regl-scatter2d: "npm:^3.2.3" + checksum: 10c0/7f7d90c218b13e26fe3745cd8639fca739797908739e7924ea1e728f0e58031508a1f3db65ae7f5da2b2c33b290c36509cfd49db3e5c430ffa54805ac51dd51a + languageName: node + linkType: hard + +"regl@npm:^2.0.0": + version: 2.1.1 + resolution: "regl@npm:2.1.1" + checksum: 10c0/68aa1c6f94cb33da612515b0e69833af67dce422e5b8d891c6d8f4c055aac1afd44e2b94dcfe90bb79fde1c5cd48501db292f5268365140765256ea3518546e1 + languageName: node + linkType: hard + +"remark-gfm@npm:^4.0.1": + version: 4.0.1 + resolution: "remark-gfm@npm:4.0.1" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-gfm: "npm:^3.0.0" + micromark-extension-gfm: "npm:^3.0.0" + remark-parse: "npm:^11.0.0" + remark-stringify: "npm:^11.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/427ecc6af3e76222662061a5f670a3e4e33ec5fffe2cabf04034da6a3f9a1bda1fc023e838a636385ba314e66e2bebbf017ca61ebea357eb0f5200fe0625a4b7 + languageName: node + linkType: hard + +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-from-markdown: "npm:^2.0.0" + micromark-util-types: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/6eed15ddb8680eca93e04fcb2d1b8db65a743dcc0023f5007265dda558b09db595a087f622062ccad2630953cd5cddc1055ce491d25a81f3317c858348a8dd38 + languageName: node + linkType: hard + +"remark-rehype@npm:^11.0.0": + version: 11.1.1 + resolution: "remark-rehype@npm:11.1.1" + dependencies: + "@types/hast": "npm:^3.0.0" + "@types/mdast": "npm:^4.0.0" + mdast-util-to-hast: "npm:^13.0.0" + unified: "npm:^11.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/68f986e8ee758d415e93babda2a0d89477c15b7c200edc23b8b1d914dd6e963c5fc151a11cbbbcfa7dd237367ff3ef86e302be90f31f37a17b0748668bd8c65b + languageName: node + linkType: hard + +"remark-stringify@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-stringify@npm:11.0.0" + dependencies: + "@types/mdast": "npm:^4.0.0" + mdast-util-to-markdown: "npm:^2.0.0" + unified: "npm:^11.0.0" + checksum: 10c0/0cdb37ce1217578f6f847c7ec9f50cbab35df5b9e3903d543e74b405404e67c07defcb23cd260a567b41b769400f6de03c2c3d9cd6ae7a6707d5c8d89ead489f + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: 10c0/b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: "npm:^5.0.0" + checksum: 10c0/e608a3ebd15356264653c32d7ecbc8fd702f94c6703ea4ac2fb81d9c359180cba0ae2e6b71faa446631ed6145454d5a56b227efc33a2d40638ac13f8beb20ee4 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 10c0/b21cb7f1fb746de8107b9febab60095187781137fd803e6a59a76d421444b1531b641bba5857f5dc011974d8a5c635d61cec49e6bd3b7fc20e01f0fafc4efbf2 + languageName: node + linkType: hard + +"resolve-protobuf-schema@npm:^2.1.0": + version: 2.1.0 + resolution: "resolve-protobuf-schema@npm:2.1.0" + dependencies: + protocol-buffers-schema: "npm:^3.3.1" + checksum: 10c0/8e656b9072b1c001952f851251413bc79d8c771c3015f607b75e1ca3b8bd7c4396068dd19cdbb3019affa03f6457d2c0fd38d981ffd714215cd2e7c2b67221a7 + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10c0/1ade1493f4642a6267d0a5e68faeac20b3d220f18c28b140343feb83694d8fed7a286852aef43689d16042c61e2ddb270be6578ad4a13990769e12065191200d + languageName: node + linkType: hard + +"resolve@npm:^0.6.1": + version: 0.6.3 + resolution: "resolve@npm:0.6.3" + checksum: 10c0/57f63aab332a668a1a424747b58851b7dddcfbd9b7a425a6050887858174943e499d1d11e3c10e676e8b0ede15ee6fc3ce919195c12d68082729db93a1517b6d + languageName: node + linkType: hard + +"resolve@npm:^1.0.0, resolve@npm:^1.1.10, resolve@npm:^1.1.5, resolve@npm:^1.1.6, resolve@npm:^1.20.0": + version: 1.22.10 + resolution: "resolve@npm:1.22.10" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^0.6.1#optional!builtin": + version: 0.6.3 + resolution: "resolve@patch:resolve@npm%3A0.6.3#optional!builtin::version=0.6.3&hash=3bafbf" + checksum: 10c0/a46adc49c7d6828028244cf4d6b9e09d8334f8f175195685c96f5f415986e1587d8a83757ebc4e706be7b8b95911046e0ddc83e9be539050add1806564eff841 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.0.0#optional!builtin, resolve@patch:resolve@npm%3A^1.1.10#optional!builtin, resolve@patch:resolve@npm%3A^1.1.5#optional!builtin, resolve@patch:resolve@npm%3A^1.1.6#optional!builtin, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin": + version: 1.22.10 + resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: 10c0/c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 + languageName: node + linkType: hard + +"right-now@npm:^1.0.0": + version: 1.0.0 + resolution: "right-now@npm:1.0.0" + checksum: 10c0/e7a7e95d490e05816643adb99ce5bcbd726f39abc56f5da6e2d148fcd81803fd1320d80e03f186d8ac02a1b07116a777660897574cfc5c9c08e65e6f7f015a17 + languageName: node + linkType: hard + +"robust-predicates@npm:^3.0.2": + version: 3.0.2 + resolution: "robust-predicates@npm:3.0.2" + checksum: 10c0/4ecd53649f1c2d49529c85518f2fa69ffb2f7a4453f7fd19c042421c7b4d76c3efb48bc1c740c8f7049346d7cb58cf08ee0c9adaae595cc23564d360adb1fde4 + languageName: node + linkType: hard + +"rolldown@npm:1.0.0-rc.17": + version: 1.0.0-rc.17 + resolution: "rolldown@npm:1.0.0-rc.17" + dependencies: + "@oxc-project/types": "npm:=0.127.0" + "@rolldown/binding-android-arm64": "npm:1.0.0-rc.17" + "@rolldown/binding-darwin-arm64": "npm:1.0.0-rc.17" + "@rolldown/binding-darwin-x64": "npm:1.0.0-rc.17" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-s390x-gnu": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-rc.17" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-rc.17" + "@rolldown/binding-openharmony-arm64": "npm:1.0.0-rc.17" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-rc.17" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-rc.17" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-rc.17" + "@rolldown/pluginutils": "npm:1.0.0-rc.17" + dependenciesMeta: + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: bin/cli.mjs + checksum: 10c0/bb99abc62ece4e34edd06d2b8eb9ffb7194dc2f0465a4329bb106cbde3006a10f1575e3580b198b793341109a2109581aed623c537c12b0c3a4ba0d72169b2fb + languageName: node + linkType: hard + +"rollup@npm:^4.43.0": + version: 4.59.0 + resolution: "rollup@npm:4.59.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.59.0" + "@rollup/rollup-android-arm64": "npm:4.59.0" + "@rollup/rollup-darwin-arm64": "npm:4.59.0" + "@rollup/rollup-darwin-x64": "npm:4.59.0" + "@rollup/rollup-freebsd-arm64": "npm:4.59.0" + "@rollup/rollup-freebsd-x64": "npm:4.59.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.59.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.59.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.59.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.59.0" + "@rollup/rollup-linux-loong64-gnu": "npm:4.59.0" + "@rollup/rollup-linux-loong64-musl": "npm:4.59.0" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.59.0" + "@rollup/rollup-linux-ppc64-musl": "npm:4.59.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.59.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.59.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.59.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.59.0" + "@rollup/rollup-linux-x64-musl": "npm:4.59.0" + "@rollup/rollup-openbsd-x64": "npm:4.59.0" + "@rollup/rollup-openharmony-arm64": "npm:4.59.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.59.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.59.0" + "@rollup/rollup-win32-x64-gnu": "npm:4.59.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.59.0" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/f38742da34cfee5e899302615fa157aa77cb6a2a1495e5e3ce4cc9c540d3262e235bbe60caa31562bbfe492b01fdb3e7a8c43c39d842d3293bcf843123b766fc + languageName: node + linkType: hard + +"root-workspace-0b6124@workspace:.": + version: 0.0.0-use.local + resolution: "root-workspace-0b6124@workspace:." + languageName: unknown + linkType: soft + +"roughjs@npm:^4.6.6": + version: 4.6.6 + resolution: "roughjs@npm:4.6.6" + dependencies: + hachure-fill: "npm:^0.5.2" + path-data-parser: "npm:^0.1.0" + points-on-curve: "npm:^0.2.0" + points-on-path: "npm:^0.2.1" + checksum: 10c0/68c11bf4516aa014cef2fe52426a9bab237c2f500d13e1a4f13b523cb5723667bf2d92b9619325efdc5bc2a193588ff5af8d51683df17cfb8720e96fe2b92b0c + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 + languageName: node + linkType: hard + +"rw@npm:1, rw@npm:^1.3.3": + version: 1.3.3 + resolution: "rw@npm:1.3.3" + checksum: 10c0/b1e1ef37d1e79d9dc7050787866e30b6ddcb2625149276045c262c6b4d53075ddc35f387a856a8e76f0d0df59f4cd58fe24707e40797ebee66e542b840ed6a53 + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sax@npm:^1.2.4": + version: 1.4.1 + resolution: "sax@npm:1.4.1" + checksum: 10c0/6bf86318a254c5d898ede6bd3ded15daf68ae08a5495a2739564eb265cd13bcc64a07ab466fb204f67ce472bb534eb8612dac587435515169593f4fffa11de7c + languageName: node + linkType: hard + +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 10c0/3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:^5.5.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10c0/e4cf10f86f168db772ae95d86ba65b3fd6c5967c94d97c708ccb463b778c2ee53b914cd7167620950fc07faf5a564e6efe903836639e512a1aa15fbc9667fa25 + languageName: node + linkType: hard + +"semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.4, semver@npm:^7.5.3, semver@npm:^7.5.4": + version: 7.7.1 + resolution: "semver@npm:7.7.1" + bin: + semver: bin/semver.js + checksum: 10c0/fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.7.4": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + +"serve-handler@npm:6.1.7": + version: 6.1.7 + resolution: "serve-handler@npm:6.1.7" + dependencies: + bytes: "npm:3.0.0" + content-disposition: "npm:0.5.2" + mime-types: "npm:2.1.18" + minimatch: "npm:3.1.5" + path-is-inside: "npm:1.0.2" + path-to-regexp: "npm:3.3.0" + range-parser: "npm:1.2.0" + checksum: 10c0/35afb68d81afd3c38d15792a5bc2451915b739bef2898a47ebd190db6a4e29846530ac00292b8008fe7297a819257c3948be2deaf4ffd32c96689e8947cf0ae9 + languageName: node + linkType: hard + +"serve@npm:14.2.6": + version: 14.2.6 + resolution: "serve@npm:14.2.6" + dependencies: + "@zeit/schemas": "npm:2.36.0" + ajv: "npm:8.18.0" + arg: "npm:5.0.2" + boxen: "npm:7.0.0" + chalk: "npm:5.0.1" + chalk-template: "npm:0.4.0" + clipboardy: "npm:3.0.0" + compression: "npm:1.8.1" + is-port-reachable: "npm:4.0.0" + serve-handler: "npm:6.1.7" + update-check: "npm:1.5.4" + bin: + serve: build/main.js + checksum: 10c0/7e1668e0d187719dbe4f3de967012ce2263c967f6135d9c630f803b0f173334e1442ab326fcc4c8e6cd4e293d8bd8c773aebab2746ecaa0fb1ab29a36079763b + languageName: node + linkType: hard + +"set-cookie-parser@npm:^2.6.0": + version: 2.7.1 + resolution: "set-cookie-parser@npm:2.7.1" + checksum: 10c0/060c198c4c92547ac15988256f445eae523f57f2ceefeccf52d30d75dedf6bff22b9c26f756bd44e8e560d44ff4ab2130b178bd2e52ef5571bf7be3bd7632d9a + languageName: node + linkType: hard + +"shallow-copy@npm:0.0.1": + version: 0.0.1 + resolution: "shallow-copy@npm:0.0.1" + checksum: 10c0/ab1c762e36fa3a02cec228e111f227be0b46457a82f123c5ef13f3bc970be16b74dbfd0f1b6ffb450a66db856832800c86713ffc1ec5733d61f59f23fe114769 + languageName: node + linkType: hard + +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10c0/7b20dbf04112c456b7fc258622dafd566553184ac9b6938dd30b943b065b21dabd3776460df534cc02480db5e1b6aec44700d985153a3da46e7db7f9bd21326d + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10c0/9abc45dee35f554ae9453098a13fdc2f1730e525a5eb33c51f096cc31f6f10a4b38074c1ebf354ae7bffa7229506083844008dfc3bb7818228568c0b2dc1fff2 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"shelljs@npm:^0.9.2": + version: 0.9.2 + resolution: "shelljs@npm:0.9.2" + dependencies: + execa: "npm:^1.0.0" + fast-glob: "npm:^3.3.2" + interpret: "npm:^1.0.0" + rechoir: "npm:^0.6.2" + bin: + shjs: bin/shjs + checksum: 10c0/2aed1e2ff344b03a36aa018326ab768e4af88e22b0f3d89fbb2f15bca1a6cf6209c6d523658ebc7fc7986675ea75c7bdbce0501201b8883f1db21b22a50d5e6d + languageName: node + linkType: hard + +"shx@npm:0.4.0": + version: 0.4.0 + resolution: "shx@npm:0.4.0" + dependencies: + minimist: "npm:^1.2.8" + shelljs: "npm:^0.9.2" + bin: + shx: lib/cli.js + checksum: 10c0/0ccff768baaed7866641d9345713560014842fb5e7a0705ebf4a9e4e5664ca5eb58fc38e6a2a4e4c6cebe1451091b6afb0277fff390647723de6e6560dca4b01 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signum@npm:^1.0.0": + version: 1.0.0 + resolution: "signum@npm:1.0.0" + checksum: 10c0/e29582d673f0e2f3b2345f7e795f2c2be192e353f80371ed79631a0708f0200dd4db7997636b51b818007cd426da156becee3316ff9fa5c63004ce672162c74b + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10c0/e18488c6a42bdfd4ac5be85b2ced3ccd0224773baae6ad42cfbb9ec74fc07f9fa8396bd35ee638084ead7a2a0818eb5e7151111544d4731ce843019dab4be47b + languageName: node + linkType: hard + +"snake-case@npm:^3.0.4": + version: 3.0.4 + resolution: "snake-case@npm:3.0.4" + dependencies: + dot-case: "npm:^3.0.4" + tslib: "npm:^2.0.3" + checksum: 10c0/ab19a913969f58f4474fe9f6e8a026c8a2142a01f40b52b79368068343177f818cdfef0b0c6b9558f298782441d5ca8ed5932eb57822439fad791d866e62cecd + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10c0/137539f8c453fa0f496ea42049ab5da4569f96781f6ac8e5bfda26937be9494f4e8891f523c5f98f0e85f71b35d74127a00c46f83f6a4f54672b58d53202565e + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 + languageName: node + linkType: hard + +"source-map@npm:^0.7.4": + version: 0.7.4 + resolution: "source-map@npm:0.7.4" + checksum: 10c0/dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc + languageName: node + linkType: hard + +"space-separated-tokens@npm:^2.0.0": + version: 2.0.2 + resolution: "space-separated-tokens@npm:2.0.2" + checksum: 10c0/6173e1d903dca41dcab6a2deed8b4caf61bd13b6d7af8374713500570aa929ff9414ae09a0519f4f8772df993300305a395d4871f35bc4ca72b6db57e1f30af8 + languageName: node + linkType: hard + +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 10c0/ecadcfe4c771890140da5023d43e190b7566d9cf8b2d238600f31bec0fc653f328da4450eb04bd59a431771a8e9cc0e118f0aa3974b683a4981b4e07abc2a5bb + languageName: node + linkType: hard + +"ssf@npm:~0.11.2": + version: 0.11.2 + resolution: "ssf@npm:0.11.2" + dependencies: + frac: "npm:~1.1.2" + checksum: 10c0/c3fd24a90dc37a9dc5c4154cb4121e27507c33ebfeee3532aaf03625756b2c006cf79c0a23db0ba16c4a6e88e1349455327867e03453fc9d54b32c546bc18ca6 + languageName: node + linkType: hard + +"stack-trace@npm:0.0.9": + version: 0.0.9 + resolution: "stack-trace@npm:0.0.9" + checksum: 10c0/dd4fc71b4b5e956550d4aeea35b74893dfa79981237ebcaa071f4a38bba78d7fd553ec402fc5a63018a52dc75c1ebe61847c3b84ef7654b3e85e44b6a5728cfd + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"static-eval@npm:^2.0.5": + version: 2.1.1 + resolution: "static-eval@npm:2.1.1" + dependencies: + escodegen: "npm:^2.1.0" + checksum: 10c0/ad8ab8f86e6f82e3ff6c80d60a4c0ccfb086082cf594fa71a5c38cb6ed59607660e7a3e0103f3583ca38716744321d487bbe55a5ae2c6a9c965b5cf4d4cf2960 + languageName: node + linkType: hard + +"std-env@npm:^4.0.0-rc.1": + version: 4.1.0 + resolution: "std-env@npm:4.1.0" + checksum: 10c0/2e14b6b490db34cb969a48d9cf7c35bca4a47653914aac2814221baae7b867a5b15940d133625c391621971f98cd2266a5dc7036669960e883f1081db2a56558 + languageName: node + linkType: hard + +"stream-parser@npm:~0.3.1": + version: 0.3.1 + resolution: "stream-parser@npm:0.3.1" + dependencies: + debug: "npm:2" + checksum: 10c0/585508801423bd6c53f6dda9d78e4b743a08ab72e8e2680431fa855ef950e59c849ec2838f2a00c59af655ff8463e90f660f9169a816e63a3ca159cf713bae5c + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.0": + version: 1.0.3 + resolution: "stream-shift@npm:1.0.3" + checksum: 10c0/939cd1051ca750d240a0625b106a2b988c45fb5a3be0cebe9a9858cb01bc1955e8c7b9fac17a9462976bea4a7b704e317c5c2200c70f0ca715a3363b9aa4fd3b + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: "npm:^1.0.2" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/1cd77409c3d7db7bc59406f6bcc9ef0783671dcbabb23597a1177c166906ef2ee7c8290f78cae73a8aec858768f189d2cb417797df5e15ec4eb5e16b3346340c + languageName: node + linkType: hard + +"string-split-by@npm:^1.0.0": + version: 1.0.0 + resolution: "string-split-by@npm:1.0.0" + dependencies: + parenthesis: "npm:^3.1.5" + checksum: 10c0/7feac22db5eb4fedc418818fe6c9277d2b2bbe8d200877ed4be629eeff6dafb686b04d87f3c49aa0b7ccaedaf5c576e31dd0e756ea82421cc48f362ece48b942 + languageName: node + linkType: hard + +"string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10c0/1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: 10c0/1c628d78f974aa7539c496029f48e7019acc32487fc695464f9d6bdfec98edd7d933a06b3216bc2016918f6e75074c611d84430a53cb0e43071597d6c1ac5e25 + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"stringify-entities@npm:^4.0.0": + version: 4.0.4 + resolution: "stringify-entities@npm:4.0.4" + dependencies: + character-entities-html4: "npm:^2.0.0" + character-entities-legacy: "npm:^3.0.0" + checksum: 10c0/537c7e656354192406bdd08157d759cd615724e9d0873602d2c9b2f6a5c0a8d0b1d73a0a08677848105c5eebac6db037b57c0b3a4ec86331117fa7319ed50448 + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10c0/1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 10c0/26abad1172d6bc48985ab9a5f96c21e440f6e7e476686de49be813b5a59b3566dccb5c525b831ec54fe348283b47f3ffb8e080bc3f965fde12e84df23f6bb7ef + languageName: node + linkType: hard + +"strip-eof@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-eof@npm:1.0.0" + checksum: 10c0/f336beed8622f7c1dd02f2cbd8422da9208fae81daf184f73656332899978919d5c0ca84dc6cfc49ad1fc4dd7badcde5412a063cf4e0d7f8ed95a13a63f68f45 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43 + languageName: node + linkType: hard + +"strongly-connected-components@npm:^1.0.1": + version: 1.0.1 + resolution: "strongly-connected-components@npm:1.0.1" + checksum: 10c0/ab29f29be3f0d659bec3901590e7e52b0fe340e1387234b8609a00740a1afc31c34e54b503954f85ca27a17be3bc36e38db62525dc51cca9126b2b67e5be3e36 + languageName: node + linkType: hard + +"style-to-js@npm:^1.0.0": + version: 1.1.16 + resolution: "style-to-js@npm:1.1.16" + dependencies: + style-to-object: "npm:1.0.8" + checksum: 10c0/578a4dff804539ec7e64d3cc8d327540befb9ad30e3cd0b6b0392f93f793f3a028f90084a9aaff088bffb87818fa2c6c153f0df576f61f9ab0b0938b582bcac7 + languageName: node + linkType: hard + +"style-to-object@npm:1.0.8": + version: 1.0.8 + resolution: "style-to-object@npm:1.0.8" + dependencies: + inline-style-parser: "npm:0.2.4" + checksum: 10c0/daa6646b1ff18258c0ca33ed281fbe73485c8391192db1b56ce89d40c93ea64507a41e8701d0dadfe771bc2f540c46c9b295135f71584c8e5cb23d6a19be9430 + languageName: node + linkType: hard + +"styled-components@npm:^6.4.1": + version: 6.4.1 + resolution: "styled-components@npm:6.4.1" + dependencies: + "@emotion/is-prop-valid": "npm:1.4.0" + css-to-react-native: "npm:3.2.0" + csstype: "npm:3.2.3" + stylis: "npm:4.3.6" + peerDependencies: + css-to-react-native: ">= 3.2.0" + react: ">= 16.8.0" + react-dom: ">= 16.8.0" + react-native: ">= 0.68.0" + peerDependenciesMeta: + css-to-react-native: + optional: true + react-dom: + optional: true + react-native: + optional: true + checksum: 10c0/33b43f55be02cecc4dd1904f0f4397c8a9f245ee67cd12893c9117651048a442555b527fdd25b4db8a0365701713cb0e91b09df16a4d46e175e3990041b45099 + languageName: node + linkType: hard + +"stylis@npm:4.3.6, stylis@npm:^4.3.6": + version: 4.3.6 + resolution: "stylis@npm:4.3.6" + checksum: 10c0/e736d484983a34f7c65d362c67dc79b7bce388054b261c2b7b23d02eaaf280617033f65d44b1ea341854f4331a5074b885668ac8741f98c13a6cfd6443ae85d0 + languageName: node + linkType: hard + +"supercluster@npm:^7.1.0": + version: 7.1.5 + resolution: "supercluster@npm:7.1.5" + dependencies: + kdbush: "npm:^3.0.0" + checksum: 10c0/bbebf45927d0019831731c94b78d1c9a1f3e2da0be9875d7ea75c6f261487e0f15d3f1822a9a49256e3c1672bdfb9138f9a5e44e2de99edb06cd1e758083e12d + languageName: node + linkType: hard + +"supercluster@npm:^8.0.1": + version: 8.0.1 + resolution: "supercluster@npm:8.0.1" + dependencies: + kdbush: "npm:^4.0.2" + checksum: 10c0/79121e6dbff67b3036ea6651f3baddb942478830ba3ecbc27455715ab5bd269de8381dc04618c0c15963346ea2ed0f81cd5f767c2978a63e2841807c73445d57 + languageName: node + linkType: hard + +"superscript-text@npm:^1.0.0": + version: 1.0.0 + resolution: "superscript-text@npm:1.0.0" + checksum: 10c0/0d4dee472ca830234c16fd56cdf7d4bf6e3b7d80aefbb2390a6e8aa9b9e304d67df39e6154f9238e6eaebfe47766aad8de6082781760f0cc16c6ee6b57f547df + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/ea1d3c275dd604c974670f63943ed9bd83623edc102430c05adb8efc56ba492746b6e95386e7831b872ec3807fd89dd8eb43f735195f37b5ec343e4234cc7e89 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"svg-arc-to-cubic-bezier@npm:^3.0.0": + version: 3.2.0 + resolution: "svg-arc-to-cubic-bezier@npm:3.2.0" + checksum: 10c0/6dddbaff9defa55a891593b48ba6bd5f1b3c89d747cf818a8342ec4cac10b06de6cd37478dc9483cba5e8d1a7d3dac2d0bbab47858d207a2d1a6a61b5acdd442 + languageName: node + linkType: hard + +"svg-parser@npm:^2.0.4": + version: 2.0.4 + resolution: "svg-parser@npm:2.0.4" + checksum: 10c0/02f6cb155dd7b63ebc2f44f36365bc294543bebb81b614b7628f1af3c54ab64f7e1cec20f06e252bf95bdde78441ae295a412c68ad1678f16a6907d924512b7a + languageName: node + linkType: hard + +"svg-path-bounds@npm:^1.0.1": + version: 1.0.2 + resolution: "svg-path-bounds@npm:1.0.2" + dependencies: + abs-svg-path: "npm:^0.1.1" + is-svg-path: "npm:^1.0.1" + normalize-svg-path: "npm:^1.0.0" + parse-svg-path: "npm:^0.1.2" + checksum: 10c0/d24bfe8fd38405956d5ab3c41fe65d8984c2791a878cd3f4d70fe278575f1ce3270c78a07f3d2d32996485e3714f0167514fdfc5a9d7239d7c7535d391d04e1e + languageName: node + linkType: hard + +"svg-path-sdf@npm:^1.1.3": + version: 1.1.3 + resolution: "svg-path-sdf@npm:1.1.3" + dependencies: + bitmap-sdf: "npm:^1.0.0" + draw-svg-path: "npm:^1.0.0" + is-svg-path: "npm:^1.0.1" + parse-svg-path: "npm:^0.1.2" + svg-path-bounds: "npm:^1.0.1" + checksum: 10c0/0eb47e0c1946fa8515a9783a682c45c4fbb7fbaf7da76eaee96721bb40560ce97356a10e9cbdf988dc257219160b60842c6cfcdea95a8709a8d2e77657e2abfa + languageName: node + linkType: hard + +"symbol-tree@npm:^3.2.4": + version: 3.2.4 + resolution: "symbol-tree@npm:3.2.4" + checksum: 10c0/dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 + languageName: node + linkType: hard + +"tabbable@npm:^6.0.0": + version: 6.2.0 + resolution: "tabbable@npm:6.2.0" + checksum: 10c0/ced8b38f05f2de62cd46836d77c2646c42b8c9713f5bd265daf0e78ff5ac73d3ba48a7ca45f348bafeef29b23da7187c72250742d37627883ef89cbd7fa76898 + languageName: node + linkType: hard + +"tapable@npm:^2.2.0, tapable@npm:^2.2.1": + version: 2.2.1 + resolution: "tapable@npm:2.2.1" + checksum: 10c0/bc40e6efe1e554d075469cedaba69a30eeb373552aaf41caeaaa45bf56ffacc2674261b106245bd566b35d8f3329b52d838e851ee0a852120acae26e622925c9 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.13 + resolution: "tar@npm:7.5.13" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/5c65b8084799bde7a791593a1c1a45d3d6ee98182e3700b24c247b7b8f8654df4191642abbdb07ff25043d45dcff35620827c3997b88ae6c12040f64bed5076b + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^7.1.4" + minimatch: "npm:^3.0.4" + checksum: 10c0/019d33d81adff3f9f1bfcff18125fb2d3c65564f437d9be539270ee74b994986abb8260c7c2ce90e8f30162178b09dbbce33c6389273afac4f36069c48521f57 + languageName: node + linkType: hard + +"through2@npm:^0.6.3": + version: 0.6.5 + resolution: "through2@npm:0.6.5" + dependencies: + readable-stream: "npm:>=1.0.33-1 <1.1.0-0" + xtend: "npm:>=4.0.0 <4.1.0-0" + checksum: 10c0/3294325d73b120ffbb8cd00e28a649a99e194cef2638bf782b6c2eb0c163b388f7b7bb908003949f58f9f6b8f771defd24b6e4df051eb410fd87931521963b98 + languageName: node + linkType: hard + +"through2@npm:^2.0.1": + version: 2.0.5 + resolution: "through2@npm:2.0.5" + dependencies: + readable-stream: "npm:~2.3.6" + xtend: "npm:~4.0.1" + checksum: 10c0/cbfe5b57943fa12b4f8c043658c2a00476216d79c014895cef1ac7a1d9a8b31f6b438d0e53eecbb81054b93128324a82ecd59ec1a4f91f01f7ac113dcb14eade + languageName: node + linkType: hard + +"tiny-invariant@npm:^1.3.3": + version: 1.3.3 + resolution: "tiny-invariant@npm:1.3.3" + checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinycolor2@npm:^1.4.2": + version: 1.6.0 + resolution: "tinycolor2@npm:1.6.0" + checksum: 10c0/9aa79a36ba2c2a87cb221453465cabacd04b9e35f9694373e846fdc78b1c768110f81e581ea41440106c0f24d9a023891d0887e8075885e790ac40eb0e74a5c1 + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.1, tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.16": + version: 0.2.16 + resolution: "tinyglobby@npm:0.2.16" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tinyqueue@npm:^2.0.3": + version: 2.0.3 + resolution: "tinyqueue@npm:2.0.3" + checksum: 10c0/d7b590088f015a94a17132fa209c2f2a80c45158259af5474901fdf5932e95ea13ff6f034bcc725a6d5f66d3e5b888b048c310229beb25ad5bebb4f0a635abf2 + languageName: node + linkType: hard + +"tinyqueue@npm:^3.0.0": + version: 3.0.0 + resolution: "tinyqueue@npm:3.0.0" + checksum: 10c0/edd6f1a6146aa3aa7bc85b44b3aacf44cddfa805b0901019272d7e9235894b4f28b2f9d09c1bce500ae48883b03708b6b871aa33920e895d2943720f7a9ad3ba + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.1.0": + version: 3.1.0 + resolution: "tinyrainbow@npm:3.1.0" + checksum: 10c0/f11cf387a26c5c9255bec141a90ac511b26172981b10c3e50053bc6700ea7d2336edcc4a3a21dbb8412fe7c013477d2ba4d7e4877800f3f8107be5105aad6511 + languageName: node + linkType: hard + +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: 10c0/f935537799c2d1922cb5d6d3805f594388f75338fe7a4a9dac41504dd539704ca4db45b883b52e7b0aa5b2fd5ddadb1452bf95cd23a69da2f793a843f9451cc9 + languageName: node + linkType: hard + +"to-float32@npm:^1.1.0": + version: 1.1.0 + resolution: "to-float32@npm:1.1.0" + checksum: 10c0/7fed0ba97b9c6280b5858d2ca60ed005961f5feda7c413c6f7eb2be224ed033244d413ab6c53bd5d603edba2642d15ba4f952ebf1e87dc1fe837182a41b7b811 + languageName: node + linkType: hard + +"to-px@npm:1.0.1": + version: 1.0.1 + resolution: "to-px@npm:1.0.1" + dependencies: + parse-unit: "npm:^1.0.1" + checksum: 10c0/bd6c2a300316700a81b602283365ce6e093ca77ebdca5611578f3fe58d9799f4a328e836ae5a034e5f007ec513712545ca93e61f4db199f9a87a8a29bb34d756 + languageName: node + linkType: hard + +"to-px@npm:^1.0.1": + version: 1.1.0 + resolution: "to-px@npm:1.1.0" + dependencies: + parse-unit: "npm:^1.0.1" + checksum: 10c0/c6bb7132a8dac1ca647f63747362f74cfeacffd3b518d011d0bb9f30b33a6261c3e3d7ec4c32fe723f2ba654344f5777a439636ee0cca8b6cf8258675f83bb7e + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 + languageName: node + linkType: hard + +"topojson-client@npm:^3.1.0": + version: 3.1.0 + resolution: "topojson-client@npm:3.1.0" + dependencies: + commander: "npm:2" + bin: + topo2geo: bin/topo2geo + topomerge: bin/topomerge + topoquantize: bin/topoquantize + checksum: 10c0/da2acba268cbf4d002483d5d81452e0d797b2fff6041fafb1d420e58973fa780a6f42041ce4c2677376ab977e5e1732b89c42a2db3c334a34f6c47f4d94b3eaa + languageName: node + linkType: hard + +"tough-cookie@npm:^4.1.2": + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" + dependencies: + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: 10c0/aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 + languageName: node + linkType: hard + +"tr46@npm:^3.0.0": + version: 3.0.0 + resolution: "tr46@npm:3.0.0" + dependencies: + punycode: "npm:^2.1.1" + checksum: 10c0/cdc47cad3a9d0b6cb293e39ccb1066695ae6fdd39b9e4f351b010835a1f8b4f3a6dc3a55e896b421371187f22b48d7dac1b693de4f6551bdef7b6ab6735dfe3b + languageName: node + linkType: hard + +"trim-lines@npm:^3.0.0": + version: 3.0.1 + resolution: "trim-lines@npm:3.0.1" + checksum: 10c0/3a1611fa9e52aa56a94c69951a9ea15b8aaad760eaa26c56a65330dc8adf99cb282fc07cc9d94968b7d4d88003beba220a7278bbe2063328eb23fb56f9509e94 + languageName: node + linkType: hard + +"trough@npm:^2.0.0": + version: 2.2.0 + resolution: "trough@npm:2.2.0" + checksum: 10c0/58b671fc970e7867a48514168894396dd94e6d9d6456aca427cc299c004fe67f35ed7172a36449086b2edde10e78a71a284ec0076809add6834fb8f857ccb9b0 + languageName: node + linkType: hard + +"ts-dedent@npm:^2.2.0": + version: 2.2.0 + resolution: "ts-dedent@npm:2.2.0" + checksum: 10c0/175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303 + languageName: node + linkType: hard + +"ts-jest@npm:^29.4.9": + version: 29.4.9 + resolution: "ts-jest@npm:29.4.9" + dependencies: + bs-logger: "npm:^0.2.6" + fast-json-stable-stringify: "npm:^2.1.0" + handlebars: "npm:^4.7.9" + json5: "npm:^2.2.3" + lodash.memoize: "npm:^4.1.2" + make-error: "npm:^1.3.6" + semver: "npm:^7.7.4" + type-fest: "npm:^4.41.0" + yargs-parser: "npm:^21.1.1" + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/transform": ^29.0.0 || ^30.0.0 + "@jest/types": ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: ">=4.3 <7" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/transform": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + bin: + ts-jest: cli.js + checksum: 10c0/901eb382817d1f48fc56b6c9b82de989f176660295695ae1fcd55f06f71d2c107766e1413ab24a59fa964c2ef79a60dd23ac1f382b05ae04f2b454fb4eb5ad4f + languageName: node + linkType: hard + +"ts-loader@npm:^9.5.7": + version: 9.5.7 + resolution: "ts-loader@npm:9.5.7" + dependencies: + chalk: "npm:^4.1.0" + enhanced-resolve: "npm:^5.0.0" + micromatch: "npm:^4.0.0" + semver: "npm:^7.3.4" + source-map: "npm:^0.7.4" + peerDependencies: + typescript: "*" + webpack: ^5.0.0 + checksum: 10c0/e68d997962046afc40fbb6131a5f329128691917bf288d18df4f95719ca1557e72f614bf078e06544ab0c970f734495a356f3eeb5f11ca18459944237b3635a1 + languageName: node + linkType: hard + +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + +"tsconfck@npm:^3.0.3": + version: 3.1.5 + resolution: "tsconfck@npm:3.1.5" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 10c0/9b62cd85d5702aa23ea50ea578d7124f3d59cc4518fcc7eacc04f4f9c9c481f720738ff8351bd4472247c0723a17dfd01af95a5b60ad623cdb8727fbe4881847 + languageName: node + linkType: hard + +"tsconfig-paths-webpack-plugin@npm:^4.2.0": + version: 4.2.0 + resolution: "tsconfig-paths-webpack-plugin@npm:4.2.0" + dependencies: + chalk: "npm:^4.1.0" + enhanced-resolve: "npm:^5.7.0" + tapable: "npm:^2.2.1" + tsconfig-paths: "npm:^4.1.2" + checksum: 10c0/495c5ab7c1cb079217d98fe25d61def01e4bab38047c7ab25ec11876cc8c697ff01f43ea6c9933181875e51e49835407fc71afd92ea6cca1ba1bebf513dfb510 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" + dependencies: + json5: "npm:^2.2.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10c0/09a5877402d082bb1134930c10249edeebc0211f36150c35e1c542e5b91f1047b1ccf7da1e59babca1ef1f014c525510f4f870de7c9bda470c73bb4e2721b3ea + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.8.0, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 10c0/8fb9a51d3f365a7de84ab7f73b653534b61b622aa6800aecdb0f1095a4a646d3f5eb295322127b6573db7982afcd40ab492d038cf825a42093a58b1e1353e0bd + languageName: node + linkType: hard + +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: 10c0/902bd57bfa30d51d4779b641c2bc403cdf1371fb9c91d3c058b0133694fcfdb817aef07a47f40faf79039eecbaa39ee9d3c532deff244f3a19ce68cea71a61e8 + languageName: node + linkType: hard + +"type-fest@npm:^2.13.0": + version: 2.19.0 + resolution: "type-fest@npm:2.19.0" + checksum: 10c0/a5a7ecf2e654251613218c215c7493574594951c08e52ab9881c9df6a6da0aeca7528c213c622bc374b4e0cb5c443aa3ab758da4e3c959783ce884c3194e12cb + languageName: node + linkType: hard + +"type-fest@npm:^4.41.0": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"type@npm:^2.7.2": + version: 2.7.3 + resolution: "type@npm:2.7.3" + checksum: 10c0/dec6902c2c42fcb86e3adf8cdabdf80e5ef9de280872b5fd547351e9cca2fe58dd2aa6d2547626ddff174145db272f62d95c7aa7038e27c11315657d781a688d + languageName: node + linkType: hard + +"typedarray-pool@npm:^1.1.0": + version: 1.2.0 + resolution: "typedarray-pool@npm:1.2.0" + dependencies: + bit-twiddle: "npm:^1.0.0" + dup: "npm:^1.0.0" + checksum: 10c0/1e1050e51470f58bd01a82cc0ab881c148e29863e3eaf7bc115033016214fc8588e6fd21354be8ef449062e5a83bfc85434c788c378b62234aa3d391fffc2957 + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 10c0/6005cb31df50eef8b1f3c780eb71a17925f3038a100d82f9406ac2ad1de5eb59f8e6decbdc145b3a1f8e5836e17b0c0002fb698b9fe2516b8f9f9ff602d36412 + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"ufo@npm:^1.5.4": + version: 1.5.4 + resolution: "ufo@npm:1.5.4" + checksum: 10c0/b5dc4dc435c49c9ef8890f1b280a19ee4d0954d1d6f9ab66ce62ce64dd04c7be476781531f952a07c678d51638d02ad4b98e16237be29149295b0f7c09cda765 + languageName: node + linkType: hard + +"ufo@npm:^1.6.3": + version: 1.6.3 + resolution: "ufo@npm:1.6.3" + checksum: 10c0/bf0e4ebff99e54da1b9c7182ac2f40475988b41faa881d579bc97bc2a0509672107b0a0e94c4b8d31a0ab8c4bf07f4aa0b469ac6da8536d56bda5b085ea2e953 + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10c0/83b0a90eca35f778e07cad9622b80c448b6aad457c9ff8e568afed978212b42930a95f9e1be943a1ffa4258a3340fbb899f41461131c05bb1d0a9c303aed8479 + languageName: node + linkType: hard + +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf + languageName: node + linkType: hard + +"undici-types@npm:~7.19.0": + version: 7.19.2 + resolution: "undici-types@npm:7.19.2" + checksum: 10c0/7159f10546f9f6c47d36776bb1bbf8671e87c1e587a6fee84ae1f111ae8de4f914efa8ca0dfcd224f4f4a9dfc3f6028f627ccb5ddaccf82d7fd54671b89fac3e + languageName: node + linkType: hard + +"undici@npm:^6.25.0": + version: 6.25.0 + resolution: "undici@npm:6.25.0" + checksum: 10c0/2597cc6689bdb02c210c557b1f85febbfda65becae6e6fc1061508e2f33734d25207f81cd8af56ada9956329eb3a7bd7431e87dcfeceba20ee87059b57dcf985 + languageName: node + linkType: hard + +"unicorn-magic@npm:^0.3.0": + version: 0.3.0 + resolution: "unicorn-magic@npm:0.3.0" + checksum: 10c0/0a32a997d6c15f1c2a077a15b1c4ca6f268d574cf5b8975e778bb98e6f8db4ef4e86dfcae4e158cd4c7e38fb4dd383b93b13eefddc7f178dea13d3ac8a603271 + languageName: node + linkType: hard + +"unified@npm:^11.0.0": + version: 11.0.5 + resolution: "unified@npm:11.0.5" + dependencies: + "@types/unist": "npm:^3.0.0" + bail: "npm:^2.0.0" + devlop: "npm:^1.0.0" + extend: "npm:^3.0.0" + is-plain-obj: "npm:^4.0.0" + trough: "npm:^2.0.0" + vfile: "npm:^6.0.0" + checksum: 10c0/53c8e685f56d11d9d458a43e0e74328a4d6386af51c8ac37a3dcabec74ce5026da21250590d4aff6733ccd7dc203116aae2b0769abc18cdf9639a54ae528dfc9 + languageName: node + linkType: hard + +"unist-util-is@npm:^6.0.0": + version: 6.0.0 + resolution: "unist-util-is@npm:6.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/9419352181eaa1da35eca9490634a6df70d2217815bb5938a04af3a662c12c5607a2f1014197ec9c426fbef18834f6371bfdb6f033040fa8aa3e965300d70e7e + languageName: node + linkType: hard + +"unist-util-position@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-position@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/dde3b31e314c98f12b4dc6402f9722b2bf35e96a4f2d463233dd90d7cde2d4928074a7a11eff0a5eb1f4e200f27fc1557e0a64a7e8e4da6558542f251b1b7400 + languageName: node + linkType: hard + +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + checksum: 10c0/dfe1dbe79ba31f589108cb35e523f14029b6675d741a79dea7e5f3d098785045d556d5650ec6a8338af11e9e78d2a30df12b1ee86529cded1098da3f17ee999e + languageName: node + linkType: hard + +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-visit-parents@npm:6.0.1" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + checksum: 10c0/51b1a5b0aa23c97d3e03e7288f0cdf136974df2217d0999d3de573c05001ef04cccd246f51d2ebdfb9e8b0ed2704451ad90ba85ae3f3177cf9772cef67f56206 + languageName: node + linkType: hard + +"unist-util-visit@npm:^5.0.0": + version: 5.0.0 + resolution: "unist-util-visit@npm:5.0.0" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-is: "npm:^6.0.0" + unist-util-visit-parents: "npm:^6.0.0" + checksum: 10c0/51434a1d80252c1540cce6271a90fd1a106dbe624997c09ed8879279667fb0b2d3a685e02e92bf66598dcbe6cdffa7a5f5fb363af8fdf90dda6c855449ae39a5 + languageName: node + linkType: hard + +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: 10c0/cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe + languageName: node + linkType: hard + +"unquote@npm:^1.1.0": + version: 1.1.1 + resolution: "unquote@npm:1.1.1" + checksum: 10c0/de59fb48cbaadc636002c6563dcb6b1bce95c91ebecb92addbc9bb47982cb03e7d8a8371c9617267b9e5746bbcb4403394139bc1310106b9ac4c26790ed57859 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.1.1": + version: 1.1.2 + resolution: "update-browserslist-db@npm:1.1.2" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/9cb353998d6d7d6ba1e46b8fa3db888822dd972212da4eda609d185eb5c3557a93fd59780ceb757afd4d84240518df08542736969e6a5d6d6ce2d58e9363aac6 + languageName: node + linkType: hard + +"update-check@npm:1.5.4": + version: 1.5.4 + resolution: "update-check@npm:1.5.4" + dependencies: + registry-auth-token: "npm:3.3.2" + registry-url: "npm:3.1.0" + checksum: 10c0/ac4b8dafa5db9b1c8ff5d0cfcc3b4c5687c390526b3218155e27173c7ca647572ea9e523dd3463523e698ef94d273768b395748da54655fe773dada59ac9c7b0 + languageName: node + linkType: hard + +"update-diff@npm:^1.1.0": + version: 1.1.0 + resolution: "update-diff@npm:1.1.0" + checksum: 10c0/d517eb23c04d15b6bbef108b622ec8fd6f57ac19b339b1598efe8ad551005c7ab1addb03eac5d247804ed981772ff5c8538d14d2b06da69c73029aeb2bb673e8 + languageName: node + linkType: hard + +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: 10c0/bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + +"util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10c0/34aa51b9874ae398c2b799c88a127701408cd581ee89ec3baa53509dd8728cbb25826f2a038f9465f8b7be446f0fbf11558862965b18d21c993684297628d4d3 + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + +"v8-to-istanbul@npm:^9.0.1": + version: 9.3.0 + resolution: "v8-to-istanbul@npm:9.3.0" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.12" + "@types/istanbul-lib-coverage": "npm:^2.0.1" + convert-source-map: "npm:^2.0.0" + checksum: 10c0/968bcf1c7c88c04df1ffb463c179558a2ec17aa49e49376120504958239d9e9dad5281aa05f2a78542b8557f2be0b0b4c325710262f3b838b40d703d5ed30c23 + languageName: node + linkType: hard + +"vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10c0/f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"vfile-message@npm:^4.0.0": + version: 4.0.2 + resolution: "vfile-message@npm:4.0.2" + dependencies: + "@types/unist": "npm:^3.0.0" + unist-util-stringify-position: "npm:^4.0.0" + checksum: 10c0/07671d239a075f888b78f318bc1d54de02799db4e9dce322474e67c35d75ac4a5ac0aaf37b18801d91c9f8152974ea39678aa72d7198758b07f3ba04fb7d7514 + languageName: node + linkType: hard + +"vfile@npm:^6.0.0": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" + dependencies: + "@types/unist": "npm:^3.0.0" + vfile-message: "npm:^4.0.0" + checksum: 10c0/e5d9eb4810623f23758cfc2205323e33552fb5972e5c2e6587babe08fe4d24859866277404fb9e2a20afb71013860d96ec806cb257536ae463c87d70022ab9ef + languageName: node + linkType: hard + +"vite-plugin-checker@npm:^0.13.0": + version: 0.13.0 + resolution: "vite-plugin-checker@npm:0.13.0" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + chokidar: "npm:^4.0.3" + npm-run-path: "npm:^6.0.0" + picocolors: "npm:^1.1.1" + picomatch: "npm:^4.0.4" + proper-lockfile: "npm:^4.1.2" + tiny-invariant: "npm:^1.3.3" + tinyglobby: "npm:^0.2.15" + vscode-uri: "npm:^3.1.0" + peerDependencies: + "@biomejs/biome": ">=1.7" + eslint: ">=9.39.4" + meow: ^13.2.0 || ^14.0.0 + optionator: ^0.9.4 + oxlint: ">=1" + stylelint: ">=16.26.1" + typescript: "*" + vite: ">=5.4.21" + vls: "*" + vti: "*" + vue-tsc: ~2.2.10 || ^3.0.0 + peerDependenciesMeta: + "@biomejs/biome": + optional: true + eslint: + optional: true + meow: + optional: true + optionator: + optional: true + oxlint: + optional: true + stylelint: + optional: true + typescript: + optional: true + vls: + optional: true + vti: + optional: true + vue-tsc: + optional: true + checksum: 10c0/85c5e5d33b96b08ef844fe66c4e648ae6bd8b6b42fa6e1910a903cb7bc726aa06c379a5784a726247a4fab1005d72b4f706eef556e19f271dbbd6a598ba0d366 + languageName: node + linkType: hard + +"vite-plugin-svgr@npm:^4.5.0": + version: 4.5.0 + resolution: "vite-plugin-svgr@npm:4.5.0" + dependencies: + "@rollup/pluginutils": "npm:^5.2.0" + "@svgr/core": "npm:^8.1.0" + "@svgr/plugin-jsx": "npm:^8.1.0" + peerDependencies: + vite: ">=2.6.0" + checksum: 10c0/3e1959fec626bb4f5a8ec13ff15bc40ffbc1c0ff38149bebe3f37dc2d67ed1f276f129ff7983e06946cf712e19996affd9d6868aa7d20d8921d1fe4449109b55 + languageName: node + linkType: hard + +"vite-tsconfig-paths@npm:^6.1.1": + version: 6.1.1 + resolution: "vite-tsconfig-paths@npm:6.1.1" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + checksum: 10c0/5e61080991418fefa08c5b98995cdcada4931ae01ac97ef9e2ee941051f61b76890a6e7ba48bed3b2a229ec06fef33a06621bba4ce457b3f4233ad31dc0c1d1b + languageName: node + linkType: hard + +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": + version: 8.0.10 + resolution: "vite@npm:8.0.10" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.32.0" + picomatch: "npm:^4.0.4" + postcss: "npm:^8.5.10" + rolldown: "npm:1.0.0-rc.17" + tinyglobby: "npm:^0.2.16" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/92188b82654f856dbe562a1b679de695bb6ca18c0f43c4c276f84a869fb78e22dedb7c2df83b5617d6afdca979c059d654b5f61a0936a45f49917f352b9325ca + languageName: node + linkType: hard + +"vite@npm:^7.3.1": + version: 7.3.1 + resolution: "vite@npm:7.3.1" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 + languageName: node + linkType: hard + +"vitest@npm:^4.1.5": + version: 4.1.5 + resolution: "vitest@npm:4.1.5" + dependencies: + "@vitest/expect": "npm:4.1.5" + "@vitest/mocker": "npm:4.1.5" + "@vitest/pretty-format": "npm:4.1.5" + "@vitest/runner": "npm:4.1.5" + "@vitest/snapshot": "npm:4.1.5" + "@vitest/spy": "npm:4.1.5" + "@vitest/utils": "npm:4.1.5" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^4.0.0-rc.1" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.1.5 + "@vitest/browser-preview": 4.1.5 + "@vitest/browser-webdriverio": 4.1.5 + "@vitest/coverage-istanbul": 4.1.5 + "@vitest/coverage-v8": 4.1.5 + "@vitest/ui": 4.1.5 + happy-dom: "*" + jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vite: + optional: false + bin: + vitest: vitest.mjs + checksum: 10c0/196eaf5e95b45a3f6d3001a2408d7dc6f146c29c873ed4e42e1ad4c9327122934fb3793a12b6ce3b7c25d355e738b20123acc0894ce30358c3370b15f4bd0865 + languageName: node + linkType: hard + +"vscode-jsonrpc@npm:8.2.0": + version: 8.2.0 + resolution: "vscode-jsonrpc@npm:8.2.0" + checksum: 10c0/0789c227057a844f5ead55c84679206227a639b9fb76e881185053abc4e9848aa487245966cc2393fcb342c4541241b015a1a2559fddd20ac1e68945c95344e6 + languageName: node + linkType: hard + +"vscode-languageserver-protocol@npm:3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-protocol@npm:3.17.5" + dependencies: + vscode-jsonrpc: "npm:8.2.0" + vscode-languageserver-types: "npm:3.17.5" + checksum: 10c0/5f38fd80da9868d706eaa4a025f4aff9c3faad34646bcde1426f915cbd8d7e8b6c3755ce3fef6eebd256ba3145426af1085305f8a76e34276d2e95aaf339a90b + languageName: node + linkType: hard + +"vscode-languageserver-textdocument@npm:~1.0.11": + version: 1.0.12 + resolution: "vscode-languageserver-textdocument@npm:1.0.12" + checksum: 10c0/534349894b059602c4d97615a1147b6c4c031141c2093e59657f54e38570f5989c21b376836f13b9375419869242e9efb4066643208b21ab1e1dee111a0f00fb + languageName: node + linkType: hard + +"vscode-languageserver-types@npm:3.17.5": + version: 3.17.5 + resolution: "vscode-languageserver-types@npm:3.17.5" + checksum: 10c0/1e1260de79a2cc8de3e46f2e0182cdc94a7eddab487db5a3bd4ee716f67728e685852707d72c059721ce500447be9a46764a04f0611e94e4321ffa088eef36f8 + languageName: node + linkType: hard + +"vscode-languageserver@npm:~9.0.1": + version: 9.0.1 + resolution: "vscode-languageserver@npm:9.0.1" + dependencies: + vscode-languageserver-protocol: "npm:3.17.5" + bin: + installServerIntoExtension: bin/installServerIntoExtension + checksum: 10c0/8a0838d77c98a211c76e54bd3a6249fc877e4e1a73322673fb0e921168d8e91de4f170f1d4ff7e8b6289d0698207afc6aba6662d4c1cd8e4bd7cae96afd6b0c2 + languageName: node + linkType: hard + +"vscode-uri@npm:^3.1.0, vscode-uri@npm:~3.1.0": + version: 3.1.0 + resolution: "vscode-uri@npm:3.1.0" + checksum: 10c0/5f6c9c10fd9b1664d71fab4e9fbbae6be93c7f75bb3a1d9d74399a88ab8649e99691223fd7cef4644376cac6e94fa2c086d802521b9a8e31c5af3e60f0f35624 + languageName: node + linkType: hard + +"vt-pbf@npm:^3.1.1, vt-pbf@npm:^3.1.3": + version: 3.1.3 + resolution: "vt-pbf@npm:3.1.3" + dependencies: + "@mapbox/point-geometry": "npm:0.1.0" + "@mapbox/vector-tile": "npm:^1.3.1" + pbf: "npm:^3.2.1" + checksum: 10c0/a568801ae25f0ffe65ef697bf520c996c8a4067f73f355c0d5815238de90322c8ca207c61220206141cfe6f5b525de875b7eb26e22979a1b768b96d03b93dca7 + languageName: node + linkType: hard + +"w3c-xmlserializer@npm:^4.0.0": + version: 4.0.0 + resolution: "w3c-xmlserializer@npm:4.0.0" + dependencies: + xml-name-validator: "npm:^4.0.0" + checksum: 10c0/02cc66d6efc590bd630086cd88252444120f5feec5c4043932b0d0f74f8b060512f79dc77eb093a7ad04b4f02f39da79ce4af47ceb600f2bf9eacdc83204b1a8 + languageName: node + linkType: hard + +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: "npm:1.0.12" + checksum: 10c0/a17e037bccd3ca8a25a80cb850903facdfed0de4864bd8728f1782370715d679fa72e0a0f5da7c1c1379365159901e5935f35be531229da53bbfc0efdabdb48e + languageName: node + linkType: hard + +"weak-map@npm:^1.0.5": + version: 1.0.8 + resolution: "weak-map@npm:1.0.8" + checksum: 10c0/56c3c044f41a3f096db55d5dec86bb57e042fa21081491be5fa406608669089c09153de66c14cc8b93c4979fe977c92316e83cba25c2f0305c870065deff042f + languageName: node + linkType: hard + +"webgl-context@npm:^2.2.0": + version: 2.2.0 + resolution: "webgl-context@npm:2.2.0" + dependencies: + get-canvas-context: "npm:^1.0.1" + checksum: 10c0/4a2f677657e340a3b48153d36a7b7487d41e745214f114dd97a357372da7cd3fc051c85ceb5024410b431251bfdaeb028eb54fb22b46290f1178d7cb0eba70ba + languageName: node + linkType: hard + +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: 10c0/228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4 + languageName: node + linkType: hard + +"whatwg-encoding@npm:^2.0.0": + version: 2.0.0 + resolution: "whatwg-encoding@npm:2.0.0" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 10c0/91b90a49f312dc751496fd23a7e68981e62f33afe938b97281ad766235c4872fc4e66319f925c5e9001502b3040dd25a33b02a9c693b73a4cbbfdc4ad10c3e3e + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^3.0.0": + version: 3.0.0 + resolution: "whatwg-mimetype@npm:3.0.0" + checksum: 10c0/323895a1cda29a5fb0b9ca82831d2c316309fede0365047c4c323073e3239067a304a09a1f4b123b9532641ab604203f33a1403b5ca6a62ef405bcd7a204080f + languageName: node + linkType: hard + +"whatwg-url@npm:^11.0.0": + version: 11.0.0 + resolution: "whatwg-url@npm:11.0.0" + dependencies: + tr46: "npm:^3.0.0" + webidl-conversions: "npm:^7.0.0" + checksum: 10c0/f7ec264976d7c725e0696fcaf9ebe056e14422eacbf92fdbb4462034609cba7d0c85ffa1aab05e9309d42969bcf04632ba5ed3f3882c516d7b093053315bf4c1 + languageName: node + linkType: hard + +"which@npm:^1.2.9": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10c0/e945a8b6bbf6821aaaef7f6e0c309d4b615ef35699576d5489b4261da9539f70393c6b2ce700ee4321c18f914ebe5644bc4631b15466ffbaad37d83151f6af59 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/449fa5c44ed120ccecfe18c433296a4978a7583bf2391c50abce13f76878d2476defde04d0f79db8165bdf432853c1f8389d0485ca6e8ebce3bbcded513d5e6a + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.1 + resolution: "which@npm:6.0.1" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/7e710e54ea36d2d6183bee2f9caa27a3b47b9baf8dee55a199b736fcf85eab3b9df7556fca3d02b50af7f3dfba5ea3a45644189836df06267df457e354da66d5 + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"widest-line@npm:^4.0.1": + version: 4.0.1 + resolution: "widest-line@npm:4.0.1" + dependencies: + string-width: "npm:^5.0.1" + checksum: 10c0/7da9525ba45eaf3e4ed1a20f3dcb9b85bd9443962450694dae950f4bdd752839747bbc14713522b0b93080007de8e8af677a61a8c2114aa553ad52bde72d0f9c + languageName: node + linkType: hard + +"wmf@npm:~1.0.1": + version: 1.0.2 + resolution: "wmf@npm:1.0.2" + checksum: 10c0/3fa5806f382632cadfe65d4ef24f7a583b0c0720171edb00e645af5248ad0bb6784e8fcee1ccd9f475a1a12a7523e2512e9c063731fbbdae14dc469e1c033d93 + languageName: node + linkType: hard + +"word@npm:~0.3.0": + version: 0.3.0 + resolution: "word@npm:0.3.0" + checksum: 10c0/c6da2a9f7a0d81a32fa6768a638d21b153da2be04f94f3964889c7cc1365d74b6ecb43b42256c3f926cd59512d8258206991c78c21000c3da96d42ff1238b840 + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 + languageName: node + linkType: hard + +"world-calendars@npm:^1.0.4": + version: 1.0.4 + resolution: "world-calendars@npm:1.0.4" + dependencies: + object-assign: "npm:^4.1.0" + checksum: 10c0/8f89412868180dd56e12bcce2d0c0f965c6f7cc0f1c9b26e21d2ced8da6f3f1ac34b648e66da63d148136479f1916776a554352ecccc815b5b14c5396aafc328 + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.0.1": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10c0/138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10c0/56fece1a4018c6a6c8e28fbc88c87e0fbf4ea8fd64fc6c63b18f4acc4bd13e0ad2515189786dd2c30d3eec9663d70f4ecf699330002f8ccb547e4a18231fc9f0 + languageName: node + linkType: hard + +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10c0/a2c282c95ef5d8e1c27b335ae897b5eca00e85590d92a3fd69a437919b7b93ff36a69ea04145da55829d2164e724bc62202cdb5f4b208b425aba0807889375c7 + languageName: node + linkType: hard + +"ws@npm:^8.11.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + +"xlsx@npm:^0.18.5": + version: 0.18.5 + resolution: "xlsx@npm:0.18.5" + dependencies: + adler-32: "npm:~1.3.0" + cfb: "npm:~1.2.1" + codepage: "npm:~1.15.0" + crc-32: "npm:~1.2.1" + ssf: "npm:~0.11.2" + wmf: "npm:~1.0.1" + word: "npm:~0.3.0" + bin: + xlsx: bin/xlsx.njs + checksum: 10c0/787cfa77034a3e86fdcde21572f1011c8976f87823a5e0ee5057f13b2f6e48f17a1710732a91b8ae15d7794945c7cba8a3ca904ea7150e028260b0ab8e1158c8 + languageName: node + linkType: hard + +"xml-name-validator@npm:^4.0.0": + version: 4.0.0 + resolution: "xml-name-validator@npm:4.0.0" + checksum: 10c0/c1bfa219d64e56fee265b2bd31b2fcecefc063ee802da1e73bad1f21d7afd89b943c9e2c97af2942f60b1ad46f915a4c81e00039c7d398b53cf410e29d3c30bd + languageName: node + linkType: hard + +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 10c0/b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + languageName: node + linkType: hard + +"xtend@npm:>=4.0.0 <4.1.0-0, xtend@npm:^4.0.0, xtend@npm:~4.0.1": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"xtend@npm:^2.1.2": + version: 2.2.0 + resolution: "xtend@npm:2.2.0" + checksum: 10c0/396c724b8ea54c7346d7ca304116dc9aa0327b4cee4782c0a42707e6022d9a6b1c1d61b056357b1ee24102d6a366a90a313cc38e79bf70452d4d205b64a1575f + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10c0/4df2842c36e468590c3691c894bc9cdbac41f520566e76e24f59401ba7d8b4811eb1e34524d57e54bc6d864bcb66baab7ffd9ca42bf1eda596618f9162b91249 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml@npm:^2.8.3": + version: 2.8.3 + resolution: "yaml@npm:2.8.3" + bin: + yaml: bin.mjs + checksum: 10c0/ddff0e11c1b467728d7eb4633db61c5f5de3d8e9373cf84d08fb0cdee03e1f58f02b9f1c51a4a8a865751695addbd465a77f73f1079be91fe5493b29c305fd77 + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10c0/f84b5e48169479d2f402239c59f084cfd1c3acc197a05c59b98bab067452e6b3ea46d4dd8ba2985ba7b3d32a343d77df0debd6b343e5dae3da2aab2cdf5886b2 + languageName: node + linkType: hard + +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10c0/ccd7e723e61ad5965fffbb791366db689572b80cca80e0f96aad968dfff4156cd7cd1ad18607afe1046d8241e6fb2d6c08bf7fa7bfb5eaec818735d8feac8f05 + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zwitch@npm:^2.0.0": + version: 2.0.4 + resolution: "zwitch@npm:2.0.4" + checksum: 10c0/3c7830cdd3378667e058ffdb4cf2bb78ac5711214e2725900873accb23f3dfe5f9e7e5a06dcdc5f29605da976fc45c26d9a13ca334d6eea2245a15e77b8fc06e + languageName: node + linkType: hard
+ {JSON.stringify(serializeSite(site), null, 2)} +
|
---
+ {children} +
- {children} -