0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-04 13:50:12 -05:00

Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Andrey Antukh 2024-12-12 10:56:20 +01:00
commit 1c76587d70
19 changed files with 1291 additions and 317 deletions

View file

@ -2,7 +2,7 @@
const config = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
staticDirs: ["../resources/public"],
addons: ["@storybook/addon-essentials", "@storybook/addon-themes"],
addons: ["@storybook/addon-essentials", "@storybook/addon-themes", "@storybook/addon-interactions"],
core: {
builder: "@storybook/builder-vite",
options: {

View file

@ -48,11 +48,13 @@
},
"devDependencies": {
"@playwright/test": "1.48.1",
"@storybook/addon-essentials": "^8.3.6",
"@storybook/addon-themes": "^8.3.6",
"@storybook/blocks": "^8.3.6",
"@storybook/react": "^8.3.6",
"@storybook/react-vite": "^8.3.6",
"@storybook/addon-essentials": "^8.4.6",
"@storybook/addon-interactions": "^8.4.6",
"@storybook/addon-themes": "^8.4.6",
"@storybook/blocks": "^8.4.6",
"@storybook/react": "^8.4.6",
"@storybook/react-vite": "^8.4.6",
"@storybook/test": "^8.4.6",
"@types/node": "^22.7.7",
"autoprefixer": "^10.4.20",
"concurrently": "^9.0.1",
@ -86,7 +88,7 @@
"sass": "^1.80.3",
"sass-embedded": "^1.80.3",
"shadow-cljs": "2.28.18",
"storybook": "^8.3.6",
"storybook": "^8.4.6",
"svg-sprite": "^2.0.4",
"typescript": "^5.6.3",
"vite": "^5.4.9",

View file

@ -9,6 +9,7 @@
[app.config :as cf]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.combobox :refer [combobox*]]
[app.main.ui.ds.controls.input :refer [input*]]
[app.main.ui.ds.controls.select :refer [select*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list]]
@ -38,6 +39,7 @@
:Loader loader*
:RawSvg raw-svg*
:Select select*
:Combobox combobox*
:Text text*
:TabSwitcher tab-switcher*
:Toast toast*

View file

@ -0,0 +1,252 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.controls.combobox
(:require-macros
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
[app.util.array :as array]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[rumext.v2 :as mf]))
(def listbox-id-index (atom 0))
(defn- get-option
[options id]
(array/find #(= id (obj/get % "id")) options))
(defn- handle-focus-change
[options focused* new-index options-nodes-refs]
(let [option (aget options new-index)
id (obj/get option "id")
nodes (mf/ref-val options-nodes-refs)
node (obj/get nodes id)]
(reset! focused* id)
(dom/scroll-into-view-if-needed! node)))
(defn- handle-selection
[focused* selected* open*]
(when-let [focused (deref focused*)]
(reset! selected* focused))
(reset! open* false)
(reset! focused* nil))
(def ^:private schema:combobox-option
[:and
[:map {:title "option"}
[:id :string]
[:icon {:optional true}
[:and :string [:fn #(contains? icon-list %)]]]
[:label {:optional true} :string]
[:aria-label {:optional true} :string]]
[:fn {:error/message "invalid data: missing required props"}
(fn [option]
(or (and (contains? option :icon)
(or (contains? option :label)
(contains? option :aria-label)))
(contains? option :label)))]])
(def ^:private schema:combobox
[:map
[:options [:vector {:min 1} schema:combobox-option]]
[:class {:optional true} :string]
[:disabled {:optional true} :boolean]
[:default-selected {:optional true} :string]
[:on-change {:optional true} fn?]])
(mf/defc combobox*
{::mf/props :obj
::mf/schema schema:combobox}
[{:keys [options class disabled default-selected on-change] :rest props}]
(let [open* (mf/use-state false)
open (deref open*)
selected* (mf/use-state default-selected)
selected (deref selected*)
focused* (mf/use-state nil)
focused (deref focused*)
has-focus* (mf/use-state false)
has-focus (deref has-focus*)
dropdown-options
(mf/use-memo
(mf/deps options selected)
(fn []
(->> options
(array/filter (fn [option]
(let [lower-option (.toLowerCase (obj/get option "id"))
lower-filter (.toLowerCase selected)]
(.includes lower-option lower-filter)))))))
on-click
(mf/use-fn
(mf/deps disabled)
(fn [event]
(dom/stop-propagation event)
(when-not disabled
(reset! has-focus* true)
(if (= "INPUT" (.-tagName (.-target event)))
(reset! open* true)
(swap! open* not)))))
on-option-click
(mf/use-fn
(mf/deps on-change)
(fn [event]
(let [node (dom/get-current-target event)
id (dom/get-data node "id")]
(reset! selected* id)
(reset! focused* nil)
(reset! open* false)
(when (fn? on-change)
(on-change id)))))
options-nodes-refs (mf/use-ref nil)
options-ref (mf/use-ref nil)
listbox-id-ref (mf/use-ref (dm/str "listbox-" (swap! listbox-id-index inc)))
listbox-id (mf/ref-val listbox-id-ref)
combobox-ref (mf/use-ref nil)
set-ref
(mf/use-fn
(fn [node id]
(let [refs (or (mf/ref-val options-nodes-refs) #js {})
refs (if node
(obj/set! refs id node)
(obj/unset! refs id))]
(mf/set-ref-val! options-nodes-refs refs))))
on-blur
(mf/use-fn
(fn [event]
(let [target (.-relatedTarget event)
outside? (not (.contains (mf/ref-val combobox-ref) target))]
(when outside?
(reset! focused* nil)
(reset! open* false)
(reset! has-focus* false)))))
on-key-down
(mf/use-fn
(mf/deps open focused disabled dropdown-options)
(fn [event]
(when-not disabled
(let [options dropdown-options
focused (deref focused*)
len (alength options)
index (array/find-index #(= (deref focused*) (obj/get % "id")) options)]
(dom/stop-propagation event)
(when (< len 0)
(reset! index len))
(cond
(and (not open) (kbd/down-arrow? event))
(reset! open* true)
open
(cond
(kbd/home? event)
(handle-focus-change options focused* 0 options-nodes-refs)
(kbd/up-arrow? event)
(let [new-index (if (= index -1)
(dec len)
(mod (- index 1) len))]
(handle-focus-change options focused* new-index options-nodes-refs))
(kbd/down-arrow? event)
(let [new-index (if (= index -1)
0
(mod (+ index 1) len))]
(handle-focus-change options focused* new-index options-nodes-refs))
(or (kbd/space? event) (kbd/enter? event))
(when (deref open*)
(dom/prevent-default event)
(handle-selection focused* selected* open*)
(when (fn? on-change)
(on-change focused)))
(kbd/esc? event)
(do (reset! open* false)
(reset! focused* nil))))))))
on-input-change
(mf/use-fn
(fn [event]
(let [value (.-value (.-currentTarget event))]
(reset! selected* value)
(reset! focused* nil)
(when (fn? on-change)
(on-change value)))))
on-focus
(mf/use-fn
(fn [_] (reset! has-focus* true)))
class (dm/str class " " (stl/css :combobox))
selected-option (get-option options selected)
icon (obj/get selected-option "icon")]
(mf/with-effect [options]
(mf/set-ref-val! options-ref options))
[:div {:ref combobox-ref
:class (stl/css-case
:combobox-wrapper true
:focused has-focus)}
[:div {:class class
:on-click on-click
:on-focus on-focus
:on-blur on-blur}
[:span {:class (stl/css-case :combobox-header true
:header-icon (some? icon))}
(when icon
[:> icon* {:id icon
:size "s"
:aria-hidden true}])
[:input {:type "text"
:role "combobox"
:aria-autocomplete "both"
:aria-expanded open
:aria-controls listbox-id
:aria-activedescendant focused
:class (stl/css :input)
:data-testid "combobox-input"
:disabled disabled
:value selected
:on-change on-input-change
:on-key-down on-key-down}]]
[:> :button {:tab-index "-1"
:aria-expanded open
:aria-controls listbox-id
:class (stl/css :button-toggle-list)
:on-click on-click}
[:> icon* {:id i/arrow
:class (stl/css :arrow)
:size "s"
:aria-hidden true
:data-testid "combobox-open-button"}]]]
(when (and open (seq dropdown-options))
[:> options-dropdown* {:on-click on-option-click
:options dropdown-options
:selected selected
:focused focused
:set-ref set-ref
:id listbox-id
:data-testid "combobox-options"}])]))

View file

@ -0,0 +1,62 @@
import { Canvas, Meta } from "@storybook/blocks";
import * as ComboboxStories from "./combobox.stories";
<Meta title="Controls/Combobox" />
# Combobox
Combobox lets users choose one option from an options menu or enter a custom value that is not listed in the menu. It combines the functionality of a dropdown menu and an input field, allowing for both selection and free-form input.
## Variants
**Text**: We will use this variant when there are enough space and icons don't add any useful context.
<Canvas of={ComboboxStories.Default} />
**Icon and text**: We will use this variant when there are enough space and icons add any useful context.
<Canvas of={ComboboxStories.WithIcons} />
## Technical notes
### Icons
Each option of `combobox*` may accept an `icon`, which must contain an [icon ID](../foundations/assets/icon.mdx).
These are available in the `app.main.ds.foundations.assets.icon` namespace.
```clj
(ns app.main.ui.foo
(:require
[app.main.ui.ds.foundations.assets.icon :as i]))
```
```clj
[:> combobox*
{:options [{ :label "Code"
:id "option-code"
:icon i/fill-content }
{ :label "Design"
:id "option-design"
:icon i/pentool }
{ :label "Menu"
:id "option-menu" }
]}]
```
<Canvas of={ComboboxStories.WithIcons} />
## Usage guidelines (design)
### Where to Use
Combobox is used in applications where users need to select from a range of text-based options or enter custom input.
### When to Use
Consider using a combobox when you have five or more options to present, and users may benefit from the ability to search or input a custom value that is not in the predefined list.
### Interaction / Behavior
- **Opening Options**: When the user clicks on the input area, a dropdown menu of options appears. Users can either scroll through the options, type to filter them, or input a new value directly.
- **Selecting an Option**: Once an option is selected or a custom value is entered, the dropdown closes, and the input field displays the chosen value.
- **Keyboard Support**: Combobox supports navigation using keyboard input, including arrow keys to navigate the list and Enter to make a selection.

View file

@ -0,0 +1,87 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "../_borders.scss" as *;
@use "../_sizes.scss" as *;
@use "../typography.scss" as *;
.combobox-wrapper {
--combobox-icon-fg-color: var(--color-foreground-secondary);
--combobox-fg-color: var(--color-foreground-primary);
--combobox-bg-color: var(--color-background-tertiary);
--combobox-outline-color: none;
--combobox-border-color: none;
@include use-typography("body-small");
position: relative;
display: grid;
grid-template-rows: auto;
gap: var(--sp-xxs);
width: 100%;
&:hover {
--combobox-bg-color: var(--color-background-quaternary);
}
}
.combobox {
display: grid;
grid-template-columns: 1fr auto;
gap: var(--sp-xs);
height: $sz-32;
width: 100%;
padding: var(--sp-s);
border: none;
border-radius: $br-8;
outline: $b-1 solid var(--combobox-outline-color);
border: $b-1 solid var(--combobox-border-color);
background: var(--combobox-bg-color);
color: var(--combobox-fg-color);
appearance: none;
&:disabled {
--combobox-bg-color: var(--color-background-primary);
--combobox-border-color: var(--color-background-quaternary);
--combobox-fg-color: var(--color-foreground-secondary);
}
}
.focused {
--combobox-outline-color: var(--color-accent-primary);
}
.arrow {
color: var(--combobox-icon-fg-color);
transform: rotate(90deg);
}
.combobox-header {
display: grid;
justify-items: start;
gap: var(--sp-xs);
}
.input {
all: unset;
@include use-typography("body-small");
background-color: transparent;
overflow: hidden;
text-align: left;
inline-size: 100%;
padding-inline-start: var(--sp-xxs);
color: var(--combobox-fg-color);
}
.header-icon {
grid-template-columns: auto 1fr;
color: var(--combobox-icon-fg-color);
}
.button-toggle-list {
all: unset;
display: flex;
}

View file

@ -0,0 +1,216 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
import * as React from "react";
import Components from "@target/components";
import { userEvent, within, expect } from "@storybook/test";
const { Combobox } = Components;
let lastValue = null;
export default {
title: "Controls/Combobox",
component: Combobox,
argTypes: {
disabled: { control: "boolean" },
},
args: {
disabled: false,
options: [
{ id: "January", label: "January" },
{ id: "February", label: "February" },
{ id: "March", label: "March" },
{ id: "April", label: "April" },
{ id: "May", label: "May" },
{ id: "June", label: "June" },
{ id: "July", label: "July" },
{ id: "August", label: "August" },
{ id: "September", label: "September" },
{ id: "October", label: "October" },
{ id: "November", label: "November" },
{ id: "December", label: "December" },
],
defaultSelected: "February",
onChange: (value) => (lastValue = value),
},
parameters: {
controls: {
exclude: ["options", "defaultSelected"],
},
},
render: ({ ...args }) => (
<div style={{ padding: "5px" }}>
<Combobox {...args} />
</div>
),
};
export const Default = {
parameters: {
docs: {
story: {
height: "450px",
},
},
},
};
export const WithIcons = {
args: {
options: [
{ id: "January", label: "January", icon: "fill-content" },
{ id: "February", label: "February", icon: "pentool" },
{ id: "March", label: "March" },
{ id: "April", label: "April" },
{ id: "May", label: "May" },
{ id: "June", label: "June" },
{ id: "July", label: "July" },
{ id: "August", label: "August" },
{ id: "September", label: "September" },
{ id: "October", label: "October" },
{ id: "November", label: "November" },
{ id: "December", label: "December" },
],
},
parameters: {
docs: {
story: {
height: "450px",
},
},
},
};
export const TestInteractions = {
...WithIcons,
play: async ({ canvasElement, step }) => {
const canvas = within(canvasElement);
const combobox = await canvas.getByRole("combobox");
const button = await canvas.getByTestId("combobox-open-button");
const input = await canvas.getByTestId("combobox-input");
const waitOptionNotPresent = async () => {
expect(canvas.queryByTestId("combobox-options")).not.toBeInTheDocument();
};
const waitOptionsPresent = async () => {
const options = await canvas.findByTestId("combobox-options");
expect(options).toBeVisible();
return options;
};
await userEvent.clear(input);
await step("Toggle dropdown on click arrow button", async () => {
await userEvent.click(button);
await waitOptionsPresent();
expect(combobox).toHaveAttribute("aria-expanded", "true");
await userEvent.click(button);
await waitOptionNotPresent();
expect(combobox).toHaveAttribute("aria-expanded", "false");
});
await step("Aria controls is set correctly", async () => {
await userEvent.click(button);
const ariaControls = combobox.getAttribute("aria-controls");
const options = await canvas.findByTestId("combobox-options");
expect(options).toHaveAttribute("id", ariaControls);
});
await step("Navigation keys", async () => {
// Arrow down
await userEvent.click(input);
await waitOptionsPresent();
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Enter}");
expect(input).toHaveValue("February");
expect(lastValue).toBe("February");
await userEvent.clear(input);
// Arrow up
await userEvent.keyboard("{ArrowDown}");
await waitOptionsPresent();
await userEvent.keyboard("{ArrowUp}");
await userEvent.keyboard("{ArrowUp}");
expect(combobox).toHaveAttribute("aria-activedescendant", "November");
await userEvent.keyboard("{Enter}");
expect(input).toHaveValue("November");
expect(lastValue).toBe("November");
await userEvent.clear(input);
// Home
await userEvent.keyboard("{ArrowDown}");
await waitOptionsPresent();
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Home}");
expect(combobox).toHaveAttribute("aria-activedescendant", "January");
await userEvent.keyboard("{Enter}");
expect(input).toHaveValue("January");
expect(lastValue).toBe("January");
await userEvent.clear(input);
});
await step("Toggle dropdown with arrow down and ESC", async () => {
userEvent.click(input);
await waitOptionsPresent();
await userEvent.keyboard("{Escape}");
expect(combobox).toHaveAttribute("aria-expanded", "false");
await waitOptionNotPresent();
await userEvent.keyboard("{ArrowDown}");
await waitOptionsPresent();
expect(combobox).toHaveAttribute("aria-expanded", "true");
await userEvent.keyboard("{Escape}");
await waitOptionNotPresent();
expect(combobox).toHaveAttribute("aria-expanded", "false");
});
await step("Filter with 'Ju' and select July", async () => {
await userEvent.type(input, "Ju");
const options = await canvas.findAllByTestId("dropdown-option");
expect(options).toHaveLength(2);
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{ArrowDown}");
await userEvent.keyboard("{Enter}");
expect(input).toHaveValue("July");
expect(lastValue).toBe("July");
});
await step("Close dropdown when focus out", async () => {
await userEvent.click(button);
await waitOptionsPresent();
await userEvent.tab();
await waitOptionNotPresent();
});
},
};

View file

@ -9,6 +9,7 @@
[app.common.data.macros :as dm]
[app.main.style :as stl])
(:require
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
[app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i]
[app.util.array :as array]
[app.util.dom :as dom]
@ -16,63 +17,6 @@
[app.util.object :as obj]
[rumext.v2 :as mf]))
(mf/defc option*
{::mf/props :obj
::mf/private true}
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
[:> :li {:value id
:class (stl/css-case :option true
:option-with-icon (some? icon)
:option-current focused)
:aria-selected selected
:ref (fn [node]
(set-ref node id))
:role "option"
:id id
:on-click on-click
:data-id id}
(when (some? icon)
[:> icon*
{:id icon
:size "s"
:class (stl/css :option-icon)
:aria-hidden (when label true)
:aria-label (when (not label) aria-label)}])
[:span {:class (stl/css :option-text)} label]
(when selected
[:> icon*
{:id i/tick
:size "s"
:class (stl/css :option-check)
:aria-hidden (when label true)}])])
(mf/defc options-dropdown*
{::mf/props :obj
::mf/private true}
[{:keys [set-ref on-click options selected focused] :rest props}]
(let [props (mf/spread-props props
{:class (stl/css :option-list)
:tab-index "-1"
:role "listbox"})]
[:> "ul" props
(for [option ^js options]
(let [id (obj/get option "id")
label (obj/get option "label")
aria-label (obj/get option "aria-label")
icon (obj/get option "icon")]
[:> option* {:selected (= id selected)
:key id
:id id
:label label
:icon icon
:aria-label aria-label
:set-ref set-ref
:focused (= id focused)
:on-click on-click}]))]))
(def ^:private schema:select-option
[:and
[:map {:title "option"}

View file

@ -14,7 +14,6 @@
--select-bg-color: var(--color-background-tertiary);
--select-outline-color: none;
--select-border-color: none;
--select-dropdown-border-color: var(--color-background-quaternary);
&:hover {
--select-bg-color: var(--color-background-quaternary);
@ -81,67 +80,3 @@
grid-template-columns: auto 1fr;
color: var(--select-icon-fg-color);
}
.option-list {
--options-dropdown-bg-color: var(--color-background-tertiary);
position: absolute;
right: 0;
top: $sz-36;
width: 100%;
background-color: var(--options-dropdown-bg-color);
border-radius: $br-8;
border: $b-1 solid var(--select-dropdown-border-color);
padding-block: var(--sp-xs);
margin-block-end: 0;
max-height: $sz-400;
overflow-y: auto;
overflow-x: hidden;
}
.option {
--select-option-fg-color: var(--color-foreground-primary);
--select-option-bg-color: unset;
&:hover {
--select-option-bg-color: var(--color-background-quaternary);
}
&[aria-selected="true"] {
--select-option-bg-color: var(--color-background-quaternary);
}
display: grid;
align-items: center;
justify-items: start;
grid-template-columns: 1fr auto;
gap: var(--sp-xs);
width: 100%;
height: $sz-32;
padding: var(--sp-s);
border-radius: $br-8;
outline: $b-1 solid var(--select-outline-color);
outline-offset: -1px;
background-color: var(--select-option-bg-color);
}
.option-with-icon {
grid-template-columns: auto 1fr auto;
}
.option-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
min-width: 0;
padding-inline-start: var(--sp-xxs);
}
.option-icon {
color: var(--select-icon-fg-color);
}
.option-current {
--select-option-outline-color: var(--color-accent-primary);
outline: $b-1 solid var(--select-option-outline-color);
}

View file

@ -0,0 +1,69 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.ds.controls.shared.options-dropdown
(:require-macros
[app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.util.object :as obj]
[rumext.v2 :as mf]))
(mf/defc option*
{::mf/props :obj
::mf/private true}
[{:keys [id label icon aria-label on-click selected set-ref focused] :rest props}]
[:> :li {:value id
:class (stl/css-case :option true
:option-with-icon (some? icon)
:option-current focused)
:aria-selected selected
:ref (fn [node]
(set-ref node id))
:role "option"
:id id
:on-click on-click
:data-id id
:data-testid "dropdown-option"}
(when (some? icon)
[:> icon*
{:id icon
:size "s"
:class (stl/css :option-icon)
:aria-hidden (when label true)
:aria-label (when (not label) aria-label)}])
[:span {:class (stl/css :option-text)} label]
(when selected
[:> icon*
{:id i/tick
:size "s"
:class (stl/css :option-check)
:aria-hidden (when label true)}])])
(mf/defc options-dropdown*
{::mf/props :obj}
[{:keys [set-ref on-click options selected focused] :rest props}]
(let [props (mf/spread-props props
{:class (stl/css :option-list)
:tab-index "-1"
:role "listbox"})]
[:> "ul" props
(for [option ^js options]
(let [id (obj/get option "id")
label (obj/get option "label")
aria-label (obj/get option "aria-label")
icon (obj/get option "icon")]
[:> option* {:selected (= id selected)
:key id
:id id
:label label
:icon icon
:aria-label aria-label
:set-ref set-ref
:focused (= id focused)
:on-click on-click}]))]))

View file

@ -0,0 +1,74 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "../../_borders.scss" as *;
@use "../../_sizes.scss" as *;
@use "../../typography.scss" as *;
.option-list {
--options-dropdown-icon-fg-color: var(--color-foreground-secondary);
--options-dropdown-bg-color: var(--color-background-tertiary);
--options-dropdown-outline-color: none;
--options-dropdown-border-color: var(--color-background-quaternary);
position: absolute;
right: 0;
top: $sz-36;
width: 100%;
background-color: var(--options-dropdown-bg-color);
border-radius: $br-8;
border: $b-1 solid var(--options-dropdown-dropdown-border-color);
padding-block: var(--sp-xs);
margin-block-end: 0;
max-height: $sz-400;
overflow-y: auto;
overflow-x: hidden;
}
.option {
--options-dropdown-fg-color: var(--color-foreground-primary);
--options-dropdown-bg-color: unset;
display: grid;
align-items: center;
justify-items: start;
grid-template-columns: 1fr auto;
gap: var(--sp-xs);
width: 100%;
height: $sz-32;
padding: var(--sp-s);
border-radius: $br-8;
outline: $b-1 solid var(--options-dropdown-outline-color);
outline-offset: -1px;
background-color: var(--options-dropdown-bg-color);
&:hover,
&[aria-selected="true"] {
--options-dropdown-bg-color: var(--color-background-quaternary);
}
}
.option-with-icon {
grid-template-columns: auto 1fr auto;
}
.option-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
min-width: 0;
padding-inline-start: var(--sp-xxs);
}
.option-icon {
color: var(--options-dropdown-icon-fg-color);
}
.option-current {
--options-dropdown-outline-color: var(--color-accent-primary);
outline: $b-1 solid var(--options-dropdown-outline-color);
}

View file

@ -284,7 +284,8 @@
(p/fmap (fn [ready?]
(when ready?
(reset! canvas-init? true)
(wasm.api/assign-canvas canvas)))))
(wasm.api/assign-canvas canvas)
(wasm.api/set-canvas-background background)))))
(fn []
(wasm.api/clear-canvas))))
@ -304,6 +305,10 @@
(when @canvas-init?
(wasm.api/set-view zoom vbox)))
(mf/with-effect [background]
(when @canvas-init?
(wasm.api/set-canvas-background background)))
(hooks/setup-dom-events zoom disable-paste in-viewport? read-only? drawing-tool drawing-path?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? read-only?)

View file

@ -345,6 +345,11 @@
(set! (.-width canvas) (* dpr (.-clientWidth ^js canvas)))
(set! (.-height canvas) (* dpr (.-clientHeight ^js canvas))))
(defn set-canvas-background
[background]
(let [rgba (rgba-from-hex background 1)]
(h/call internal-module "_set_canvas_background" rgba)))
(defonce module
(delay
(if (exists? js/dynamicImport)

View file

@ -5,6 +5,13 @@ __metadata:
version: 8
cacheKey: 10c0
"@adobe/css-tools@npm:^4.4.0":
version: 4.4.1
resolution: "@adobe/css-tools@npm:4.4.1"
checksum: 10c0/1a68ad9af490f45fce7b6e50dd2d8ac0c546d74431649c0d42ee4ceb1a9fa057fae0a7ef1e148effa12d84ec00ed71869ebfe0fb1dcdcc80bfcb6048c12abcc0
languageName: node
linkType: hard
"@ampproject/remapping@npm:^2.2.0":
version: 2.3.0
resolution: "@ampproject/remapping@npm:2.3.0"
@ -15,7 +22,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0":
"@babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0":
version: 7.26.2
resolution: "@babel/code-frame@npm:7.26.2"
dependencies:
@ -633,21 +640,19 @@ __metadata:
languageName: node
linkType: hard
"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.3.0":
version: 0.3.0
resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.3.0"
"@joshwooding/vite-plugin-react-docgen-typescript@npm:0.4.2":
version: 0.4.2
resolution: "@joshwooding/vite-plugin-react-docgen-typescript@npm:0.4.2"
dependencies:
glob: "npm:^7.2.0"
glob-promise: "npm:^4.2.0"
magic-string: "npm:^0.27.0"
react-docgen-typescript: "npm:^2.2.2"
peerDependencies:
typescript: ">= 4.3.x"
vite: ^3.0.0 || ^4.0.0 || ^5.0.0
vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 10c0/31098ad8fcc2440437534599c111d9f2951dd74821e8ba46c521b969bae4c918d830b7bb0484efbad29a51711bb62d3bc623d5a1ed5b1695b5b5594ea9dd4ca0
checksum: 10c0/355d13ad92a9da786b561a25250e6c94a8e51d235ced345e54ebfe709abc45ab60c2a8d06599df6ec0441fba01720f189883429943cb62dff9a4c31b59f0939c
languageName: node
linkType: hard
@ -1243,9 +1248,9 @@ __metadata:
languageName: node
linkType: hard
"@storybook/addon-actions@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-actions@npm:8.4.2"
"@storybook/addon-actions@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-actions@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@types/uuid": "npm:^9.0.1"
@ -1253,143 +1258,158 @@ __metadata:
polished: "npm:^4.2.2"
uuid: "npm:^9.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/ac89e6e0517efa2f8d6442f8fc0b1c3912bfc1ad50e03cccd06721d3bb52d11f472126a590c746cd565875d8ac11c63457de94e7c1ff6a3f8151b3c6488802d6
storybook: ^8.4.6
checksum: 10c0/80b2feceacb4ebe7f2be06b2fe3f49ded5ee08ca8bd036ff47a65d45d8796d29081ccadd0526984c8022bcfa24348e0ad4ce3f37cee4a60a928bae372bfc8afe
languageName: node
linkType: hard
"@storybook/addon-backgrounds@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-backgrounds@npm:8.4.2"
"@storybook/addon-backgrounds@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-backgrounds@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
memoizerific: "npm:^1.11.3"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/8fac73fafe7974c1710b0565e0fab56b9a3ee35190a06b63e9ae996c5f5a0d214ec755f7e88de8fb8d7493eb022ad820952dffbfc417f2949c07750faea18e46
storybook: ^8.4.6
checksum: 10c0/2125d6905bf44194adf79e92698753d5e4ff75fac1ffbba1fc95ae705ba9ac8dc6ca9249c9a862aa05ea207d916d23142faefa759bb9ce21c6e16f0e329d28d2
languageName: node
linkType: hard
"@storybook/addon-controls@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-controls@npm:8.4.2"
"@storybook/addon-controls@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-controls@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
dequal: "npm:^2.0.2"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/8de00a60c34de7972efc3c882912c1b135d4867045783742515741380750a58f4ce0e98139328804adcf1c2926110ca88e2df1135c3b1b03a05b20c97494ef7a
storybook: ^8.4.6
checksum: 10c0/f5f0ab2de8de80c8c3726de81802042cc29a6f2ec50de3b8bd463286c9056e87800e4ea9b350c6a41ce4c4175a11cb7d3d490da5cfc20bbf2a2e3549f77a82a7
languageName: node
linkType: hard
"@storybook/addon-docs@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-docs@npm:8.4.2"
"@storybook/addon-docs@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-docs@npm:8.4.6"
dependencies:
"@mdx-js/react": "npm:^3.0.0"
"@storybook/blocks": "npm:8.4.2"
"@storybook/csf-plugin": "npm:8.4.2"
"@storybook/react-dom-shim": "npm:8.4.2"
"@storybook/blocks": "npm:8.4.6"
"@storybook/csf-plugin": "npm:8.4.6"
"@storybook/react-dom-shim": "npm:8.4.6"
react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/ba8046898006b7e0c088ee26e378eff7e9aa315eb0c7ddf6b6d15ad6eea0d544d39674868b2b5ef5c89e64e1dee5501ceceaf2a3854636e88b99f5eaafe4b239
storybook: ^8.4.6
checksum: 10c0/ae53bf71048fe7476862ae733f0f765a22d0d1da32457f7ca7e3bdd23bb1cd452c56bc4e1f586cf978599c3f5acb835caeb569ff394eaec09d3259382f4954be
languageName: node
linkType: hard
"@storybook/addon-essentials@npm:^8.3.6":
version: 8.4.2
resolution: "@storybook/addon-essentials@npm:8.4.2"
"@storybook/addon-essentials@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-essentials@npm:8.4.6"
dependencies:
"@storybook/addon-actions": "npm:8.4.2"
"@storybook/addon-backgrounds": "npm:8.4.2"
"@storybook/addon-controls": "npm:8.4.2"
"@storybook/addon-docs": "npm:8.4.2"
"@storybook/addon-highlight": "npm:8.4.2"
"@storybook/addon-measure": "npm:8.4.2"
"@storybook/addon-outline": "npm:8.4.2"
"@storybook/addon-toolbars": "npm:8.4.2"
"@storybook/addon-viewport": "npm:8.4.2"
"@storybook/addon-actions": "npm:8.4.6"
"@storybook/addon-backgrounds": "npm:8.4.6"
"@storybook/addon-controls": "npm:8.4.6"
"@storybook/addon-docs": "npm:8.4.6"
"@storybook/addon-highlight": "npm:8.4.6"
"@storybook/addon-measure": "npm:8.4.6"
"@storybook/addon-outline": "npm:8.4.6"
"@storybook/addon-toolbars": "npm:8.4.6"
"@storybook/addon-viewport": "npm:8.4.6"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/746470edd1f9ebbb9bd4f48461bc24141c215fe146b335efe14fbb289d381faf3935d55e4e25c251777b940caf827c06574062bb18bb1b95e2c9c85b89c8635a
storybook: ^8.4.6
checksum: 10c0/b8fb83e018fcb1e8cad04b371af5f8ce9933e3a500a78a889715ecfe4efd9faa52acce2d0f97fb04fe9ae0898e661112816c052bfe9b5f01189938b122055a44
languageName: node
linkType: hard
"@storybook/addon-highlight@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-highlight@npm:8.4.2"
"@storybook/addon-highlight@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-highlight@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/6838bab4434da65e85de70908f0ca09e9aa93facdb8fa6799100d711a55cbc69744c131f8994e910efd6bf74507bcc035f7ca4f3367c3003fc5799212160fc65
storybook: ^8.4.6
checksum: 10c0/67a23a5e3b8f7740c7101e8fa886f3f9c6c61b6db3cb3430d2c805231f7ad170d2d926c12e7c9bfc4af327c5abac5b4155f4c0d70ea423b04704fe3def845acc
languageName: node
linkType: hard
"@storybook/addon-measure@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-measure@npm:8.4.2"
"@storybook/addon-interactions@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-interactions@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6"
"@storybook/test": "npm:8.4.6"
polished: "npm:^4.2.2"
ts-dedent: "npm:^2.2.0"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/42e4bc2df354dba10217385687ac20fb355f4e1a2a7390812081d6b387151b67bca868211794e531c1e112dc4ce50c70dffa55c8f4338b0bd860d59363d58d5b
languageName: node
linkType: hard
"@storybook/addon-measure@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-measure@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
tiny-invariant: "npm:^1.3.1"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/3458cce88b41bb54f74f5affc610b07f486db07709ac13a1b84b7b17fb0d9c2b3fce9325b69a9f60a8d446ae0befc530a4de7d5dc133f4d818d438ff4378cf61
storybook: ^8.4.6
checksum: 10c0/fd05b49fdb102a991fc696a0f75fde08d372b692778340ab2abc2c73fbd31a07dfa27a7a9d775dda7baaa9bd8a18972ed0bd86e9ce27948afb0305778f7b5a95
languageName: node
linkType: hard
"@storybook/addon-outline@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-outline@npm:8.4.2"
"@storybook/addon-outline@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-outline@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/042693756b2d00e9454f544d35d1e6a638e7adc7e165c92a4a0c99578a0ff001357c54826fa0e8fe7dbedcd10e62b60045fd30e1cd2b4e3dff4521aece9e6426
storybook: ^8.4.6
checksum: 10c0/62600a9f4164a8d91118d37cd7be4f4dd871e849a156ba7728f463bc2cfc5a8a233df09055dd5e5733a042fde7a63b08616cb3c61b26c363c1e2d4ce20d92584
languageName: node
linkType: hard
"@storybook/addon-themes@npm:^8.3.6":
version: 8.4.2
resolution: "@storybook/addon-themes@npm:8.4.2"
"@storybook/addon-themes@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/addon-themes@npm:8.4.6"
dependencies:
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/53e93495011dd1c19d65370e3b9ce2b5ab9051558822311b5e03f36a7611210eaaee62c63866788e69ff96532a309d35626dcb02fbcee2f2963f4db1edd3a5d5
storybook: ^8.4.6
checksum: 10c0/a20d84f1b8b72935b24a16dd1d859554398f4b391974ef04815e70c644901ff98910b8ed6d7415f939223853e4dfee61f466e7c3041c409195fd97f3176992c1
languageName: node
linkType: hard
"@storybook/addon-toolbars@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-toolbars@npm:8.4.2"
"@storybook/addon-toolbars@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-toolbars@npm:8.4.6"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/f5808d3863867295475295423a397108d41b01ac6564b0a18241c2f1e3ecf9e67c4326c663917c72315f6c60f203dc0d0e93b4778af4e7071a047a6001e1eef5
storybook: ^8.4.6
checksum: 10c0/6525e71aaa3870ae97d407b662323022ade98859f89975110f5fb4a1d3f34b6c918d47fcc8a6a271f4a77acfcaadc963a846a83ebc6c748b37df50422ad60e7e
languageName: node
linkType: hard
"@storybook/addon-viewport@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/addon-viewport@npm:8.4.2"
"@storybook/addon-viewport@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/addon-viewport@npm:8.4.6"
dependencies:
memoizerific: "npm:^1.11.3"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/676dc421781afcb50598172d9a1391604e73b9d35989b23e33674ec81b16c5dbd123a6a43098134927e1d2ffb3353fd32231261025cfc5e50ebb1259329f8ec1
storybook: ^8.4.6
checksum: 10c0/824438cc44a45f90748ac5f20ac148a36d975a94fa89504a583e0e1188de8c574e042ad3cd537bc16ddb30d4e44e90f5a63263239b13419aec5334e2ece18cd0
languageName: node
linkType: hard
"@storybook/blocks@npm:8.4.2, @storybook/blocks@npm:^8.3.6":
version: 8.4.2
resolution: "@storybook/blocks@npm:8.4.2"
"@storybook/blocks@npm:8.4.6, @storybook/blocks@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/blocks@npm:8.4.6"
dependencies:
"@storybook/csf": "npm:^0.1.11"
"@storybook/icons": "npm:^1.2.12"
@ -1397,42 +1417,42 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.2
storybook: ^8.4.6
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
checksum: 10c0/63cb3ed08742409041dca7fea3b476fb16675ddcc11b602ba4b20f61ab92993e15bc020e14e92398d4e2ea3bf62186274f5737c1c88ae26f9e717168f71441d5
checksum: 10c0/36d79c3aeb3d27f4ba966d62302e13fc17fd7b450dbfbcf538adfc6df3cfecb13c92f9d2542871fa747a77d7c770e413b358623049135355fb01454d6eb52d9a
languageName: node
linkType: hard
"@storybook/builder-vite@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/builder-vite@npm:8.4.2"
"@storybook/builder-vite@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/builder-vite@npm:8.4.6"
dependencies:
"@storybook/csf-plugin": "npm:8.4.2"
"@storybook/csf-plugin": "npm:8.4.6"
browser-assert: "npm:^1.2.1"
ts-dedent: "npm:^2.0.0"
peerDependencies:
storybook: ^8.4.2
vite: ^4.0.0 || ^5.0.0
checksum: 10c0/646f7cfbc77e7aaced8d2f0922e1b54662f6cb3c7602c5db97a419fd724033f8d68a332ebad9bf14641f7e7edec42797685bdfa24c666475f3bbeb23fe20f941
storybook: ^8.4.6
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/36998ffea04023a9f634ebbafe0d1ab3bd3e7c7fec8e8e6c4caef3ce0c94ce01fa44f332f40d0053edb788548f95096baf8561cd35c23fe3c9bcfd872f74f631
languageName: node
linkType: hard
"@storybook/components@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/components@npm:8.4.2"
"@storybook/components@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/components@npm:8.4.6"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/36ffb5f73dceb481e76fa6e006118f382c23c8081cf47500f0eea8566e902a11d3fd219b599a9f622358f17652c445f71bc8d7a80e0d43f28cd85d60f7b4a15f
checksum: 10c0/1622b2f12b6d18e5c495a623deb2930888b3e8b173a271cbe42a7cbd6e14e80b736c57792ea97d5269dff0e6c0db40385d3ea80ab6e46d4cb6e104aee6cac6bc
languageName: node
linkType: hard
"@storybook/core@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/core@npm:8.4.2"
"@storybook/core@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/core@npm:8.4.6"
dependencies:
"@storybook/csf": "npm:^0.1.11"
better-opn: "npm:^3.0.2"
@ -1450,18 +1470,18 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
checksum: 10c0/75a9a9e00d98bb77d171a2738fdc0e9ab1cfbd760410b95c286368c7f25bbb756b61bd23b89d512707a02e450b81ecbdc72bf05e63fb18ea35509a2a806b0e21
checksum: 10c0/1e30268eec18458dd78ed4b97fb12ac47b2c3cb41ffcbe9e9f5934b3f0c83b0bfcb0c0d508926344779383cc5260f992dcd534ffffab3f05425c7cee8c90687c
languageName: node
linkType: hard
"@storybook/csf-plugin@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/csf-plugin@npm:8.4.2"
"@storybook/csf-plugin@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/csf-plugin@npm:8.4.6"
dependencies:
unplugin: "npm:^1.3.1"
peerDependencies:
storybook: ^8.4.2
checksum: 10c0/8fc0db319b8ebe6a445989cc0c5576c7186da086f84d5fad30615e1e527f31bcf562e12b4f31ec85e3fd188aa676116d4023232dcca4441c7c517cda0ac23bf0
storybook: ^8.4.6
checksum: 10c0/d771f36ee768c6ff62ecd930c6ff64a4ba45bdbb7f7fb41e5f4ffd02204e3f54b17ed091049b265a6d371922bf599bfe749eb9deabfcd7e2b4fb5a5444655241
languageName: node
linkType: hard
@ -1491,43 +1511,55 @@ __metadata:
languageName: node
linkType: hard
"@storybook/manager-api@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/manager-api@npm:8.4.2"
"@storybook/instrumenter@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/instrumenter@npm:8.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@vitest/utils": "npm:^2.1.1"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/306e16af4a4babf18d7b32335f974ac969a3f9139534f37e3ce238462f69f1ad52e3091a45bf76b1cbdd8f3cf989836c8433cad6cbb2c3eb4dcbc7ccb0f8ae82
storybook: ^8.4.6
checksum: 10c0/602017872236124dc9dfa77d6bc2c5987d540063f15c7ace83bf91060d9343fdbe113a61cba44e17cae2247aeeb69875ebf45ff66ce9c28d364d2d3638eb3ec8
languageName: node
linkType: hard
"@storybook/preview-api@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/preview-api@npm:8.4.2"
"@storybook/manager-api@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/manager-api@npm:8.4.6"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/7b54c1962d27d32f29a3839660098ad8995cfcf31d4bde3662cff69d7a06cc4d315dad92f565901e3b0ebd7bf12fa8995cc625a71f13c34d82a4529412d8f83c
checksum: 10c0/5921ec72df0be765bd398aa906186c9b121a8b3415a7e1a10014a8d17c44aec386b59de3d240017bfc925be00c40a4da8d26991b5fa39023f23ba8efe1b0d58e
languageName: node
linkType: hard
"@storybook/react-dom-shim@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/react-dom-shim@npm:8.4.2"
"@storybook/preview-api@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/preview-api@npm:8.4.6"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/63967f4813c75e410634bff20189b5a670a061cfeeaa601ec07f0de82e2b4955af292836030d5a8432c3c7e48968285e121ed2bb55d2b5c70d17dbb4ada3c051
languageName: node
linkType: hard
"@storybook/react-dom-shim@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/react-dom-shim@npm:8.4.6"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.2
checksum: 10c0/f4cc8d3cb557c8e84f62047680af926570f170a87aec7775830b91c4793c7afee84092ef6cd9c518dbd0ab9311139a4698f1477f35d21bc4d1462c6bd54105c5
storybook: ^8.4.6
checksum: 10c0/b97c6faa3adc3efe1b7b6f5e38476e040c0a988b14db68e368d704c68f3f4d4bf7866b36607c118a0483242921b34944b5f5f72614d9852476476f6ead462e5c
languageName: node
linkType: hard
"@storybook/react-vite@npm:^8.3.6":
version: 8.4.2
resolution: "@storybook/react-vite@npm:8.4.2"
"@storybook/react-vite@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/react-vite@npm:8.4.6"
dependencies:
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.3.0"
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.4.2"
"@rollup/pluginutils": "npm:^5.0.2"
"@storybook/builder-vite": "npm:8.4.2"
"@storybook/react": "npm:8.4.2"
"@storybook/builder-vite": "npm:8.4.6"
"@storybook/react": "npm:8.4.6"
find-up: "npm:^5.0.0"
magic-string: "npm:^0.30.0"
react-docgen: "npm:^7.0.0"
@ -1536,43 +1568,101 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.2
vite: ^4.0.0 || ^5.0.0
checksum: 10c0/7e04112b0678a2bfd9fb913eb9161055fdb30d6ee34983294632eaae72067a16727e84801bede7b76cd7f7e0c6005021011432ea394c1a82fd73de5c6fb7b567
storybook: ^8.4.6
vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/9f81a19461dbbf11932a13f8fb611dbcd95fbfa695ee5536daf7e078bf0feb5ddda2738606073826131e3fee710e230dce9042e3f7f985203392376aa8407643
languageName: node
linkType: hard
"@storybook/react@npm:8.4.2, @storybook/react@npm:^8.3.6":
version: 8.4.2
resolution: "@storybook/react@npm:8.4.2"
"@storybook/react@npm:8.4.6, @storybook/react@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/react@npm:8.4.6"
dependencies:
"@storybook/components": "npm:8.4.2"
"@storybook/components": "npm:8.4.6"
"@storybook/global": "npm:^5.0.0"
"@storybook/manager-api": "npm:8.4.2"
"@storybook/preview-api": "npm:8.4.2"
"@storybook/react-dom-shim": "npm:8.4.2"
"@storybook/theming": "npm:8.4.2"
"@storybook/manager-api": "npm:8.4.6"
"@storybook/preview-api": "npm:8.4.6"
"@storybook/react-dom-shim": "npm:8.4.6"
"@storybook/theming": "npm:8.4.6"
peerDependencies:
"@storybook/test": 8.4.2
"@storybook/test": 8.4.6
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.2
storybook: ^8.4.6
typescript: ">= 4.2.x"
peerDependenciesMeta:
"@storybook/test":
optional: true
typescript:
optional: true
checksum: 10c0/a5ce045dae71c2a039c1ac4411c07b4a51574a6f607c6c6f105e87147410b32e7d882b3f225d6fa78ddc29423881aee76727826d2b960a61f913f7d849fdcc1f
checksum: 10c0/1441f8ab3be91757647c6b1a05eb1ef0d78a454ffd14b01a14fdde00e92a8be8fc7c8408c4670b46bc20a5a04995514f0890e98ed6ee35c362ff36141da02f02
languageName: node
linkType: hard
"@storybook/theming@npm:8.4.2":
version: 8.4.2
resolution: "@storybook/theming@npm:8.4.2"
"@storybook/test@npm:8.4.6, @storybook/test@npm:^8.4.6":
version: 8.4.6
resolution: "@storybook/test@npm:8.4.6"
dependencies:
"@storybook/csf": "npm:^0.1.11"
"@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6"
"@testing-library/dom": "npm:10.4.0"
"@testing-library/jest-dom": "npm:6.5.0"
"@testing-library/user-event": "npm:14.5.2"
"@vitest/expect": "npm:2.0.5"
"@vitest/spy": "npm:2.0.5"
peerDependencies:
storybook: ^8.4.6
checksum: 10c0/fbf7c2ac7773a7fe18145876eb67491ce90b000ba5f8e364a319569e56d56e706fdd1c7ef24d3ab2ffa3dfcdb92377d8050c8ffbd457d2d8b613aba2a4845a04
languageName: node
linkType: hard
"@storybook/theming@npm:8.4.6":
version: 8.4.6
resolution: "@storybook/theming@npm:8.4.6"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/8765a25952273f87f65018159228fa448a0bb6fa38486650344ddc076cd895546ab3b88d35a6e7f80d3223635e28d59f82510922c589a36a7c6afc33c5bcc0d7
checksum: 10c0/7d9c8e5ef2c1d974cd5258301350a2345890326e7be7a5ed6bdd0db70fd1648c0bbb8ee1d905f8e66fa57b75c47aefe7ec9772ec0bfb9691d127dcc19286e4c9
languageName: node
linkType: hard
"@testing-library/dom@npm:10.4.0":
version: 10.4.0
resolution: "@testing-library/dom@npm:10.4.0"
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"
chalk: "npm:^4.1.0"
dom-accessibility-api: "npm:^0.5.9"
lz-string: "npm:^1.5.0"
pretty-format: "npm:^27.0.2"
checksum: 10c0/0352487720ecd433400671e773df0b84b8268fb3fe8e527cdfd7c11b1365b398b4e0eddba6e7e0c85e8d615f48257753283fccec41f6b986fd6c85f15eb5f84f
languageName: node
linkType: hard
"@testing-library/jest-dom@npm:6.5.0":
version: 6.5.0
resolution: "@testing-library/jest-dom@npm:6.5.0"
dependencies:
"@adobe/css-tools": "npm:^4.4.0"
aria-query: "npm:^5.0.0"
chalk: "npm:^3.0.0"
css.escape: "npm:^1.5.1"
dom-accessibility-api: "npm:^0.6.3"
lodash: "npm:^4.17.21"
redent: "npm:^3.0.0"
checksum: 10c0/fd5936a547f04608d8de15a7de3ae26516f21023f8f45169b10c8c8847015fd20ec259b7309f08aa1031bcbc37c6e5e6f532d1bb85ef8f91bad654193ec66a4c
languageName: node
linkType: hard
"@testing-library/user-event@npm:14.5.2":
version: 14.5.2
resolution: "@testing-library/user-event@npm:14.5.2"
peerDependencies:
"@testing-library/dom": ">=7.21.4"
checksum: 10c0/68a0c2aa28a3c8e6eb05cafee29705438d7d8a9427423ce5064d44f19c29e89b5636de46dd2f28620fb10abba75c67130185bbc3aa23ac1163a227a5f36641e1
languageName: node
linkType: hard
@ -1607,6 +1697,13 @@ __metadata:
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.18.0":
version: 7.20.5
resolution: "@types/babel__core@npm:7.20.5"
@ -1662,16 +1759,6 @@ __metadata:
languageName: node
linkType: hard
"@types/glob@npm:^7.1.3":
version: 7.2.0
resolution: "@types/glob@npm:7.2.0"
dependencies:
"@types/minimatch": "npm:*"
"@types/node": "npm:*"
checksum: 10c0/a8eb5d5cb5c48fc58c7ca3ff1e1ddf771ee07ca5043da6e4871e6757b4472e2e73b4cfef2644c38983174a4bc728c73f8da02845c28a1212f98cabd293ecae98
languageName: node
linkType: hard
"@types/mdx@npm:^2.0.0":
version: 2.0.13
resolution: "@types/mdx@npm:2.0.13"
@ -1679,14 +1766,7 @@ __metadata:
languageName: node
linkType: hard
"@types/minimatch@npm:*":
version: 5.1.2
resolution: "@types/minimatch@npm:5.1.2"
checksum: 10c0/83cf1c11748891b714e129de0585af4c55dd4c2cafb1f1d5233d79246e5e1e19d1b5ad9e8db449667b3ffa2b6c80125c429dbee1054e9efb45758dbc4e118562
languageName: node
linkType: hard
"@types/node@npm:*, @types/node@npm:^22.7.7":
"@types/node@npm:^22.7.7":
version: 22.9.0
resolution: "@types/node@npm:22.9.0"
dependencies:
@ -1716,6 +1796,18 @@ __metadata:
languageName: node
linkType: hard
"@vitest/expect@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/expect@npm:2.0.5"
dependencies:
"@vitest/spy": "npm:2.0.5"
"@vitest/utils": "npm:2.0.5"
chai: "npm:^5.1.1"
tinyrainbow: "npm:^1.2.0"
checksum: 10c0/08cb1b0f106d16a5b60db733e3d436fa5eefc68571488eb570dfe4f599f214ab52e4342273b03dbe12331cc6c0cdc325ac6c94f651ad254cd62f3aa0e3d185aa
languageName: node
linkType: hard
"@vitest/expect@npm:2.1.4":
version: 2.1.4
resolution: "@vitest/expect@npm:2.1.4"
@ -1747,6 +1839,15 @@ __metadata:
languageName: node
linkType: hard
"@vitest/pretty-format@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/pretty-format@npm:2.0.5"
dependencies:
tinyrainbow: "npm:^1.2.0"
checksum: 10c0/236c0798c5170a0b5ad5d4bd06118533738e820b4dd30079d8fbcb15baee949d41c60f42a9f769906c4a5ce366d7ef11279546070646c0efc03128c220c31f37
languageName: node
linkType: hard
"@vitest/pretty-format@npm:2.1.4, @vitest/pretty-format@npm:^2.1.4":
version: 2.1.4
resolution: "@vitest/pretty-format@npm:2.1.4"
@ -1756,6 +1857,15 @@ __metadata:
languageName: node
linkType: hard
"@vitest/pretty-format@npm:2.1.8":
version: 2.1.8
resolution: "@vitest/pretty-format@npm:2.1.8"
dependencies:
tinyrainbow: "npm:^1.2.0"
checksum: 10c0/1dc5c9b1c7c7e78e46a2a16033b6b20be05958bbebc5a5b78f29e32718c80252034804fccd23f34db6b3583239db47e68fc5a8e41942c54b8047cc3b4133a052
languageName: node
linkType: hard
"@vitest/runner@npm:2.1.4":
version: 2.1.4
resolution: "@vitest/runner@npm:2.1.4"
@ -1777,6 +1887,15 @@ __metadata:
languageName: node
linkType: hard
"@vitest/spy@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/spy@npm:2.0.5"
dependencies:
tinyspy: "npm:^3.0.0"
checksum: 10c0/70634c21921eb271b54d2986c21d7ab6896a31c0f4f1d266940c9bafb8ac36237846d6736638cbf18b958bd98e5261b158a6944352742accfde50b7818ff655e
languageName: node
linkType: hard
"@vitest/spy@npm:2.1.4":
version: 2.1.4
resolution: "@vitest/spy@npm:2.1.4"
@ -1786,6 +1905,18 @@ __metadata:
languageName: node
linkType: hard
"@vitest/utils@npm:2.0.5":
version: 2.0.5
resolution: "@vitest/utils@npm:2.0.5"
dependencies:
"@vitest/pretty-format": "npm:2.0.5"
estree-walker: "npm:^3.0.3"
loupe: "npm:^3.1.1"
tinyrainbow: "npm:^1.2.0"
checksum: 10c0/0d1de748298f07a50281e1ba058b05dcd58da3280c14e6f016265e950bd79adab6b97822de8f0ea82d3070f585654801a9b1bcf26db4372e51cf7746bf86d73b
languageName: node
linkType: hard
"@vitest/utils@npm:2.1.4":
version: 2.1.4
resolution: "@vitest/utils@npm:2.1.4"
@ -1797,6 +1928,17 @@ __metadata:
languageName: node
linkType: hard
"@vitest/utils@npm:^2.1.1":
version: 2.1.8
resolution: "@vitest/utils@npm:2.1.8"
dependencies:
"@vitest/pretty-format": "npm:2.1.8"
loupe: "npm:^3.1.2"
tinyrainbow: "npm:^1.2.0"
checksum: 10c0/d4a29ecd8f6c24c790e4c009f313a044d89e664e331bc9c3cfb57fe1380fb1d2999706dbbfc291f067d6c489602e76d00435309fbc906197c0d01f831ca17d64
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.10":
version: 0.8.10
resolution: "@xmldom/xmldom@npm:0.8.10"
@ -1931,6 +2073,13 @@ __metadata:
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"
@ -1969,6 +2118,22 @@ __metadata:
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
"aria-query@npm:^5.0.0":
version: 5.3.2
resolution: "aria-query@npm:5.3.2"
checksum: 10c0/003c7e3e2cff5540bf7a7893775fc614de82b0c5dde8ae823d47b7a28a9d4da1f7ed85f340bdb93d5649caa927755f0e31ecc7ab63edfdfc00c8ef07e505e03e
languageName: node
linkType: hard
"arr-diff@npm:^4.0.0":
version: 4.0.0
resolution: "arr-diff@npm:4.0.0"
@ -2469,7 +2634,7 @@ __metadata:
languageName: node
linkType: hard
"chai@npm:^5.1.2":
"chai@npm:^5.1.1, chai@npm:^5.1.2":
version: 5.1.2
resolution: "chai@npm:5.1.2"
dependencies:
@ -2493,7 +2658,17 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^4.1.2":
"chalk@npm:^3.0.0":
version: 3.0.0
resolution: "chalk@npm:3.0.0"
dependencies:
ansi-styles: "npm:^4.1.0"
supports-color: "npm:^7.1.0"
checksum: 10c0/ee650b0a065b3d7a6fda258e75d3a86fc8e4effa55871da730a9e42ccb035bf5fd203525e5a1ef45ec2582ecc4f65b47eb11357c526b84dd29a14fb162c414d2
languageName: node
linkType: hard
"chalk@npm:^4.1.0, chalk@npm:^4.1.2":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
@ -3071,6 +3246,13 @@ __metadata:
languageName: node
linkType: hard
"css.escape@npm:^1.5.1":
version: 1.5.1
resolution: "css.escape@npm:1.5.1"
checksum: 10c0/5e09035e5bf6c2c422b40c6df2eb1529657a17df37fda5d0433d722609527ab98090baf25b13970ca754079a0f3161dd3dfc0e743563ded8cfa0749d861c1525
languageName: node
linkType: hard
"css@npm:^3.0.0":
version: 3.0.0
resolution: "css@npm:3.0.0"
@ -3304,7 +3486,7 @@ __metadata:
languageName: node
linkType: hard
"dequal@npm:^2.0.2":
"dequal@npm:^2.0.2, dequal@npm:^2.0.3":
version: 2.0.3
resolution: "dequal@npm:2.0.3"
checksum: 10c0/f98860cdf58b64991ae10205137c0e97d384c3a4edc7f807603887b7c4b850af1224a33d88012009f150861cbee4fa2d322c4cc04b9313bee312e47f6ecaa888
@ -3378,6 +3560,20 @@ __metadata:
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
"dom-accessibility-api@npm:^0.6.3":
version: 0.6.3
resolution: "dom-accessibility-api@npm:0.6.3"
checksum: 10c0/10bee5aa514b2a9a37c87cd81268db607a2e933a050074abc2f6fa3da9080ebed206a320cbc123567f2c3087d22292853bdfdceaffdd4334ffe2af9510b29360
languageName: node
linkType: hard
"dom-helpers@npm:^5.1.3":
version: 5.2.1
resolution: "dom-helpers@npm:5.2.1"
@ -4301,11 +4497,13 @@ __metadata:
"@penpot/svgo": "penpot/svgo#c6fba7a4dcfbc27b643e7fc0c94fc98cf680b77b"
"@penpot/text-editor": "portal:./text-editor"
"@playwright/test": "npm:1.48.1"
"@storybook/addon-essentials": "npm:^8.3.6"
"@storybook/addon-themes": "npm:^8.3.6"
"@storybook/blocks": "npm:^8.3.6"
"@storybook/react": "npm:^8.3.6"
"@storybook/react-vite": "npm:^8.3.6"
"@storybook/addon-essentials": "npm:^8.4.6"
"@storybook/addon-interactions": "npm:^8.4.6"
"@storybook/addon-themes": "npm:^8.4.6"
"@storybook/blocks": "npm:^8.4.6"
"@storybook/react": "npm:^8.4.6"
"@storybook/react-vite": "npm:^8.4.6"
"@storybook/test": "npm:^8.4.6"
"@tokens-studio/sd-transforms": "npm:^0.16.1"
"@types/node": "npm:^22.7.7"
autoprefixer: "npm:^10.4.20"
@ -4358,7 +4556,7 @@ __metadata:
sax: "npm:^1.4.1"
shadow-cljs: "npm:2.28.18"
source-map-support: "npm:^0.5.21"
storybook: "npm:^8.3.6"
storybook: "npm:^8.4.6"
style-dictionary: "npm:4.0.0-prerelease.34"
svg-sprite: "npm:^2.0.4"
tdigest: "npm:^0.1.2"
@ -4550,17 +4748,6 @@ __metadata:
languageName: node
linkType: hard
"glob-promise@npm:^4.2.0":
version: 4.2.2
resolution: "glob-promise@npm:4.2.2"
dependencies:
"@types/glob": "npm:^7.1.3"
peerDependencies:
glob: ^7.1.6
checksum: 10c0/3eb01bed2901539365df6a4d27800afb8788840647d01f9bf3500b3de756597f2ff4b8c823971ace34db228c83159beca459dc42a70968d4e9c8200ed2cc96bd
languageName: node
linkType: hard
"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.3, glob@npm:^10.4.2":
version: 10.4.5
resolution: "glob@npm:10.4.5"
@ -4593,7 +4780,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^7.1.3, glob@npm:^7.2.0, glob@npm:^7.2.3":
"glob@npm:^7.1.3, glob@npm:^7.2.3":
version: 7.2.3
resolution: "glob@npm:7.2.3"
dependencies:
@ -5695,7 +5882,7 @@ __metadata:
languageName: node
linkType: hard
"loupe@npm:^3.1.0, loupe@npm:^3.1.2":
"loupe@npm:^3.1.0, loupe@npm:^3.1.1, loupe@npm:^3.1.2":
version: 3.1.2
resolution: "loupe@npm:3.1.2"
checksum: 10c0/b13c02e3ddd6a9d5f8bf84133b3242de556512d824dddeea71cce2dbd6579c8f4d672381c4e742d45cf4423d0701765b4a6e5fbc24701def16bc2b40f8daa96a
@ -5741,6 +5928,15 @@ __metadata:
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.27.0":
version: 0.27.0
resolution: "magic-string@npm:0.27.0"
@ -5953,7 +6149,7 @@ __metadata:
languageName: node
linkType: hard
"min-indent@npm:^1.0.1":
"min-indent@npm:^1.0.0, min-indent@npm:^1.0.1":
version: 1.0.1
resolution: "min-indent@npm:1.0.1"
checksum: 10c0/7e207bd5c20401b292de291f02913230cb1163abca162044f7db1d951fa245b174dc00869d40dd9a9f32a885ad6a5f3e767ee104cf278f399cb4e92d3f582d5c
@ -7003,6 +7199,17 @@ __metadata:
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-time@npm:^1.1.0":
version: 1.1.0
resolution: "pretty-time@npm:1.1.0"
@ -7254,6 +7461,13 @@ __metadata:
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-lifecycles-compat@npm:^3.0.4":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"
@ -7373,6 +7587,16 @@ __metadata:
languageName: node
linkType: hard
"redent@npm:^3.0.0":
version: 3.0.0
resolution: "redent@npm:3.0.0"
dependencies:
indent-string: "npm:^4.0.0"
strip-indent: "npm:^3.0.0"
checksum: 10c0/d64a6b5c0b50eb3ddce3ab770f866658a2b9998c678f797919ceb1b586bab9259b311407280bd80b804e2a7c7539b19238ae6a2a20c843f1a7fcff21d48c2eae
languageName: node
linkType: hard
"regenerator-runtime@npm:^0.14.0":
version: 0.14.1
resolution: "regenerator-runtime@npm:0.14.1"
@ -8288,11 +8512,11 @@ __metadata:
languageName: node
linkType: hard
"storybook@npm:^8.3.6":
version: 8.4.2
resolution: "storybook@npm:8.4.2"
"storybook@npm:^8.4.6":
version: 8.4.6
resolution: "storybook@npm:8.4.6"
dependencies:
"@storybook/core": "npm:8.4.2"
"@storybook/core": "npm:8.4.6"
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
@ -8302,7 +8526,7 @@ __metadata:
getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs
storybook: ./bin/index.cjs
checksum: 10c0/54791f44de53d465a74c44ec16255ebe5248156eee54b768fdcc12a7556e1b6e2a23c9c5c5eec0c3fcc71c3820398999ede5042f711a851b0ca9c71e65c8ab19
checksum: 10c0/e15249718c1efab3d3d05f3152df28fc8f7e2e988bf7414cd4abf2adfb5d6c3b802f05dad5be0521c30d0ba43e55abf516e6f874b0671e0d1e84a7096cb47d3d
languageName: node
linkType: hard
@ -8479,6 +8703,15 @@ __metadata:
languageName: node
linkType: hard
"strip-indent@npm:^3.0.0":
version: 3.0.0
resolution: "strip-indent@npm:3.0.0"
dependencies:
min-indent: "npm:^1.0.0"
checksum: 10c0/ae0deaf41c8d1001c5d4fbe16cb553865c1863da4fae036683b474fa926af9fc121e155cb3fc57a68262b2ae7d5b8420aa752c97a6428c315d00efe2a3875679
languageName: node
linkType: hard
"strip-indent@npm:^4.0.0":
version: 4.0.0
resolution: "strip-indent@npm:4.0.0"
@ -8745,7 +8978,7 @@ __metadata:
languageName: node
linkType: hard
"tinyspy@npm:^3.0.2":
"tinyspy@npm:^3.0.0, tinyspy@npm:^3.0.2":
version: 3.0.2
resolution: "tinyspy@npm:3.0.2"
checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0
@ -8861,7 +9094,7 @@ __metadata:
languageName: node
linkType: hard
"ts-dedent@npm:^2.0.0":
"ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0":
version: 2.2.0
resolution: "ts-dedent@npm:2.2.0"
checksum: 10c0/175adea838468cc2ff7d5e97f970dcb798bbcb623f29c6088cb21aa2880d207c5784be81ab1741f56b9ac37840cbaba0c0d79f7f8b67ffe61c02634cafa5c303

25
render-wasm/README.md Normal file
View file

@ -0,0 +1,25 @@
# render-wasm
Canvas-based WebAssembly render engine for Penpot.
This is a Rust crate that targets [Emscripten](https://emscripten.org/) (`wasm32-unknown-emscripten`). Underneath, it uses Skia via [custom binaries](https://github.com/penpot/skia-binaries/releases/) of the [rust-skia crate](https://github.com/rust-skia/rust-skia).
## How to build
With the [Penpot Development Environment](https://help.penpot.app/technical-guide/developer/devenv/) running, create a new tab in the tmux.
```sh
cd penpot/render-wasm
./build
```
The build script will compile the project and copy the `.js` and `.wasm` files to their correct location within the frontend app.
Edit your local `frontend/resources/public/js/config.js` to add the following flags:
- `enable-feature-render-wasm` to enable this render engine.
- `enable-render-wasm-dpr` (optional), to enable using the device pixel ratio.
## Docs
- [Serialization](./docs/serialization.md)

View file

@ -0,0 +1,41 @@
# Serialization
## Paths
Paths are made of segments of **28 bytes** each. The layout (assuming positions in a `Uint8Array`) is the following:
| Offset | Length (bytes) | Data Type | Field |
| ------ | -------------- | --------- | ------- |
| 0 | 2 | `u16` | Command |
| 2 | 2 | `u16` | Flags |
| 4 | 4 | `f32` | `c1_x` |
| 8 | 4 | `f32` | `c1_y` |
| 12 | 4 | `f32` | `c2_x` |
| 16 | 4 | `f32` | `c2_y` |
| 20 | 4 | `f32` | `x` |
| 24 | 4 | `f32` | `y` |
**Command** can be one of these values:
- `:move-to`: `1`
- `:line-to`: `2`
- `:curve-to`: `3`
- `:close-path`: `4`
**Flags** is not being used at the moment.
## Gradient stops
Gradient stops are serialized in a `Uint8Array`, each stop taking **5 bytes**.
| Offset | Length (bytes) | Data Type | Field |
| ------ | -------------- | --------- | ----------- |
| 0 | 1 | `u8` | Red |
| 1 | 1 | `u8` | Green |
| 2 | 1 | `u8` | Blue |
| 3 | 1 | `u8` | Alpha |
| 4 | 1 | `u8` | Stop Offset |
**Red**, **Green**, **Blue** and **Alpha** are the RGBA components of the stop.
**Stop offset** is the offset, being integer values ranging from `0` to `100` (both inclusive).

View file

@ -49,6 +49,14 @@ pub extern "C" fn set_render_options(debug: u32, dpr: f32) {
render_state.set_dpr(dpr);
}
#[no_mangle]
pub extern "C" fn set_canvas_background(raw_color: u32) {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");
let color = skia::Color::new(raw_color);
state.set_background_color(color);
}
#[no_mangle]
pub unsafe extern "C" fn render() {
let state = unsafe { STATE.as_mut() }.expect("got an invalid state pointer");

View file

@ -49,6 +49,7 @@ pub(crate) struct RenderState {
options: RenderOptions,
pub viewbox: Viewbox,
images: ImageStore,
background_color: skia::Color,
}
impl RenderState {
@ -72,6 +73,7 @@ impl RenderState {
options: RenderOptions::default(),
viewbox: Viewbox::new(width as f32, height as f32),
images: ImageStore::new(),
background_color: skia::Color::TRANSPARENT,
}
}
@ -97,6 +99,11 @@ impl RenderState {
}
}
pub fn set_background_color(&mut self, color: skia::Color) {
self.background_color = color;
let _ = self.render_all_from_cache();
}
pub fn resize(&mut self, width: i32, height: i32) {
let dpr_width = (width as f32 * self.options.dpr()).floor() as i32;
let dpr_height = (height as f32 * self.options.dpr()).floor() as i32;
@ -136,7 +143,7 @@ impl RenderState {
.reset_matrix();
self.final_surface
.canvas()
.clear(skia::Color::TRANSPARENT)
.clear(self.background_color)
.reset_matrix();
self.debug_surface
.canvas()

View file

@ -1,4 +1,6 @@
use std::collections::HashMap;
use skia_safe as skia;
use uuid::Uuid;
use crate::render::RenderState;
@ -57,4 +59,9 @@ impl<'a> State<'a> {
pub fn current_shape(&'a mut self) -> Option<&'a mut Shape> {
self.current_shape.as_deref_mut()
}
pub fn set_background_color(&mut self, color: skia::Color) {
self.render_state.set_background_color(color);
self.render_all(true);
}
}