├── .formatter.exs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── lib ├── live_elements.ex └── live_elements │ └── custom_elements_helpers.ex ├── mix.exs ├── mix.lock ├── test ├── live_elements_test.exs └── test_helper.exs └── testbed ├── .formatter.exs ├── .gitignore ├── .tool-versions ├── README.md ├── assets ├── css │ └── app.css ├── custom-elements.json ├── js │ ├── app.js │ ├── foo-input.js │ ├── pie-chart.ts │ ├── simple-guy.ts │ └── todo-list.ts ├── package-lock.json ├── package.json ├── tailwind.config.js └── vendor │ └── topbar.js ├── config ├── config.exs ├── dev.exs ├── prod.exs ├── runtime.exs └── test.exs ├── lib ├── live_elements_testbed.ex ├── live_elements_testbed │ ├── application.ex │ └── mailer.ex ├── live_elements_testbed_web.ex └── live_elements_testbed_web │ ├── components │ ├── core_components.ex │ ├── layouts.ex │ └── layouts │ │ ├── app.html.heex │ │ └── root.html.heex │ ├── controllers │ ├── error_html.ex │ ├── error_json.ex │ ├── page_controller.ex │ ├── page_html.ex │ └── page_html │ │ └── home.html.heex │ ├── endpoint.ex │ ├── gettext.ex │ ├── live │ ├── data_table.ex │ ├── data_table.html.heex │ ├── eat_pie.ex │ ├── eat_pie.html.heex │ ├── form_associated.ex │ ├── simple_guy.ex │ ├── simple_guy.html.heex │ ├── todo_component.ex │ ├── todo_component.html.heex │ ├── todo_live.ex │ ├── todo_live.html.heex │ ├── with_component.ex │ └── with_component.html.heex │ ├── router.ex │ └── telemetry.ex ├── mix.exs ├── mix.lock ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot └── static │ ├── favicon.ico │ └── robots.txt └── test ├── live_elements_testbed_web ├── controllers │ ├── error_html_test.exs │ ├── error_json_test.exs │ └── page_controller_test.exs └── features │ ├── data_table_test.exs │ ├── form_associated_test.exs │ ├── simple_guy_test.exs │ └── todo_list_test.exs ├── support └── conn_case.ex └── test_helper.exs /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Integration Tests 2 | 3 | on: push 4 | 5 | jobs: 6 | test: 7 | name: Build and run tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout project 11 | uses: actions/checkout@v3 12 | - name: Set up Elixir 13 | uses: erlef/setup-beam@v1 14 | with: 15 | otp-version: '26.2.1' # Define the OTP version [required] 16 | elixir-version: '1.16.3-otp-26' # Define the elixir version [required] 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Install dependencies 20 | run: mix deps.get 21 | - name: Install testbed dependencies 22 | run: mix deps.get 23 | working-directory: ./testbed 24 | - name: install testbed assets 25 | run: npm install 26 | working-directory: testbed/assets 27 | - name: build testbed javascript 28 | run: mix esbuild default 29 | working-directory: ./testbed 30 | - name: Run elixir tests 31 | run: mix test 32 | working-directory: ./testbed 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | live_elements-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | 28 | testbed/priv/heroicons 29 | testbed/priv/static/assets -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Launch Scout, LLC 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiveElements 2 | 3 | ## Goals 4 | The goal of LiveElements is to improve the ergonomics of integrating custom HTML elements with [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view). 5 | 6 | ## Non-goals 7 | This project assumes 8 | you are using custom elements on a page served by phoenix. If you are building custom elements 9 | that need to talk to a phoenix app but live on a page not served by phoenix (on a statically generated site for example), you may want to check out [LiveState](https://hexdocs.pm/live_state) instead. 10 | 11 | ## Description 12 | 13 | We accomplish our goal by creating helper functions to make 14 | working with custom elements just as easy as any other LiveView functional component. 15 | 16 | For example, let's say you have a `` custom element that has an attribute 17 | that takes a list of todos, and emits an `add_todo` Custom Event. LiveElements will 18 | generate a helper function (details below) which will wrap the custom element like 19 | so: 20 | 21 | ```heex 22 | <.todo_list todos={@todos}> 23 | ``` 24 | 25 | Serialization of todos to json happens automatically. In your live view you handle the `add_todo` event just like any other live view event: 26 | 27 | ```elixir 28 | def handle_event("add_todo", %{"todo" => todo}, %{assigns: %{todos: todos}} = socket) do 29 | {:noreply, socket |> assign(todos: todos ++ [todo])} 30 | end 31 | ``` 32 | 33 | ## Installation 34 | 35 | ### Install hex package 36 | 37 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 38 | by adding `live_elements` to your list of dependencies in `mix.exs`: 39 | 40 | ```elixir 41 | def deps do 42 | [ 43 | {:live_elements, "~> 0.2.1"} 44 | ] 45 | end 46 | ``` 47 | 48 | ### Install Custom Events hook 49 | 50 | Presuming your custom element emits custom events you want to handle in live view, you'll need to install the `phoenix-custom-event-hook` npm package and add the hook 51 | to the live socket. 52 | 53 | To install (from project dir): 54 | 55 | ``` 56 | npm install --prefix assets phoenix-custom-event-hook 57 | ``` 58 | 59 | Then, in `app.js` (or wherever you set up your live socket): 60 | 61 | ```js 62 | import PhoenixCustomEventHook from 'phoenix-custom-event-hook'; 63 | 64 | let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 65 | let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: {PhoenixCustomEventHook}}) 66 | ``` 67 | 68 | # Usage 69 | 70 | In the LiveViews where you want to call custom element helper functions do: 71 | 72 | ``` 73 | use LiveElements.CustomElementsHelpers 74 | ``` 75 | 76 | This will add helper functions for any custom elements found in your manifest file, if 77 | you have one configured, as well as importing the `custom_element` macro. 78 | 79 | ## Producing helper functions from a custom elements manifest file 80 | 81 | LiveElements can consume a [custom elements manifest file](https://github.com/webcomponents/custom-elements-manifest) to produce helper functions 82 | at compile time. To do so, in your config: 83 | 84 | ```elixir 85 | config :live_elements, 86 | custom_elements_manifest: Path.expand("../assets/custom-elements.json", __DIR__) 87 | ``` 88 | 89 | To produce a manifest file automatically from your custom element source code, you might want to check out the [custom element analyzer](https://custom-elements-manifest.open-wc.org/analyzer/getting-started/) from open-wc.org. 90 | 91 | ## Using the `custom_element` macro 92 | 93 | If you are using a library that does not have a manifest, or don't wish to use one, you can 94 | also use the `custom_element` macro like so: 95 | 96 | ```elixir 97 | custom_element :bx_data_table, events: ["bx-table-header-cell-sort"] 98 | ``` 99 | 100 | This would then allow you to call `<.bx_data_table>` in your live view and `handle_event("bx-table-header-cell-sort", ...)` to respond to events. 101 | 102 | # Example 103 | 104 | The [live_elements_testbed](https://github.com/launchscout/live_elements_testbed) contains an 105 | example phoenix liveview app demonstrating all of the features of live_elements, as well as 106 | integration tests for the live_elements project. 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /lib/live_elements.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElements do 2 | @moduledoc """ 3 | Documentation for `LiveElements`. 4 | """ 5 | 6 | @doc """ 7 | Hello world. 8 | 9 | ## Examples 10 | 11 | iex> LiveElements.hello() 12 | :world 13 | 14 | """ 15 | def hello do 16 | :world 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/live_elements/custom_elements_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElements.CustomElementsHelpers do 2 | import Phoenix.Component 3 | alias LiveElements.CustomElementsHelpers 4 | 5 | defmacro __using__(_opts) do 6 | imports = 7 | quote do 8 | require LiveElements.CustomElementsHelpers 9 | import LiveElements.CustomElementsHelpers 10 | end 11 | 12 | manifest_defs = 13 | for custom_element <- CustomElementsHelpers.custom_elements() do 14 | %{"tagName" => tag_name} = custom_element 15 | events = Map.get(custom_element, "events", []) 16 | event_names = events |> Enum.map(& &1["name"]) |> Enum.join(",") 17 | function_name = function_name(tag_name) 18 | 19 | quote do 20 | def unquote(function_name)(assigns) do 21 | LiveElements.CustomElementsHelpers.custom_element_tag( 22 | assigns, 23 | unquote(tag_name), 24 | unquote(event_names) 25 | ) 26 | end 27 | end 28 | end 29 | 30 | [imports | manifest_defs] 31 | end 32 | 33 | def function_name(tag_name) do 34 | tag_name |> String.replace("-", "_") |> String.to_atom() 35 | end 36 | 37 | def custom_elements do 38 | ce_manifest_path = Application.get_env(:live_elements, :custom_elements_manifest) 39 | find_custom_elements(ce_manifest_path) 40 | end 41 | 42 | def serialize(assigns) do 43 | assigns |> Enum.map(fn {key, value} -> {key, serialize_value(value)} end) 44 | end 45 | 46 | def serialize_value(value) when is_binary(value), do: value 47 | 48 | def serialize_value(value) when is_number(value), do: value 49 | 50 | def serialize_value(%Phoenix.LiveComponent.CID{cid: cid}), do: cid 51 | 52 | def serialize_value(value), do: Jason.encode!(value) 53 | 54 | def find_custom_elements(nil), do: [] 55 | 56 | def find_custom_elements(ce_manifest_path) do 57 | %{"modules" => modules} = File.read!(ce_manifest_path) |> Jason.decode!() 58 | 59 | modules 60 | |> Enum.map(& &1["declarations"]) 61 | |> Enum.concat() 62 | |> Enum.filter(& &1["customElement"]) 63 | end 64 | 65 | def custom_event_hook() do 66 | Application.get_env(:live_elements, :custom_event_hook, "PhoenixCustomEventHook") 67 | end 68 | 69 | def tagify(function_name), do: function_name |> Atom.to_string() |> String.replace("_", "-") 70 | 71 | defmacro custom_element(function_name, options \\ []) do 72 | tag_name = tagify(function_name) 73 | events = Keyword.get(options, :events, []) 74 | 75 | quote do 76 | def unquote(function_name)(assigns) do 77 | LiveElements.CustomElementsHelpers.custom_element_tag( 78 | assigns, 79 | unquote(tag_name), 80 | unquote(events |> Macro.escape() |> Enum.join(",")) 81 | ) 82 | end 83 | end 84 | end 85 | 86 | def custom_element_tag(assigns, tag_name, events) do 87 | attrs = 88 | assigns 89 | |> maybe_generate_id() 90 | |> Map.put("phx-hook", custom_event_hook()) 91 | |> Map.put("phx-send-events", events) 92 | |> assigns_to_attributes() 93 | |> serialize() 94 | 95 | # Taken from `dynamic_tag` while awaiting fix for https://github.com/phoenixframework/phoenix_live_view/issues/3341 96 | 97 | assigns = 98 | assigns 99 | |> assign( 100 | tag_name: tag_name, 101 | escaped_attrs: Phoenix.LiveView.HTMLEngine.attributes_escape(attrs) 102 | ) 103 | 104 | if assigns[:inner_block] && assigns.inner_block != [] do 105 | ~H""" 106 | <%= {:safe, [?<, @tag_name]} %><%= @escaped_attrs %><%= {:safe, [?>]} %><%= render_slot(@inner_block) %><%= {:safe, [?<, ?/, @tag_name, ?>]} %> 107 | """ 108 | else 109 | ~H""" 110 | <%= {:safe, [?<, @tag_name]} %><%= @escaped_attrs %><%= {:safe, [?/, ?>]} %> 111 | """ 112 | end 113 | end 114 | 115 | defp maybe_generate_id(%{id: _id} = assigns), do: assigns 116 | defp maybe_generate_id(assigns), do: Map.put(assigns, :id, UUID.uuid1()) 117 | end 118 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule LiveElements.MixProject do 2 | use Mix.Project 3 | 4 | @description "A package to make custom elements and Phoenix LiveView so happy together" 5 | 6 | def project do 7 | [ 8 | app: :live_elements, 9 | version: "0.2.4", 10 | elixir: "~> 1.14", 11 | description: @description, 12 | start_permanent: Mix.env() == :prod, 13 | deps: deps(), 14 | package: [ 15 | licenses: ["MIT"], 16 | links: %{"Github" => "https://github.com/launchscout/live_elements"} 17 | ], 18 | docs: [ 19 | main: "readme", 20 | extras: ["README.md"] 21 | ] 22 | ] 23 | end 24 | 25 | # Run "mix help compile.app" to learn about applications. 26 | def application do 27 | [ 28 | extra_applications: [:logger] 29 | ] 30 | end 31 | 32 | # Run "mix help deps" to learn about dependencies. 33 | defp deps do 34 | [ 35 | {:phoenix, ">= 1.7.1"}, 36 | {:ex_doc, ">= 0.0.0", only: :dev}, 37 | {:phoenix_live_view, "~> 1.0"}, 38 | {:jason, ">= 0.0.0"}, 39 | {:uuid, "~> 1.1"} 40 | ] 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"}, 3 | "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, 4 | "ex_doc": {:hex, :ex_doc, "0.31.1", "8a2355ac42b1cc7b2379da9e40243f2670143721dd50748bf6c3b1184dae2089", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3178c3a407c557d8343479e1ff117a96fd31bafe52a039079593fb0524ef61b0"}, 5 | "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, 6 | "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, 7 | "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, 8 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, 9 | "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, 10 | "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, 11 | "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"}, 12 | "phoenix_html": {:hex, :phoenix_html, "4.2.0", "83a4d351b66f472ebcce242e4ae48af1b781866f00ef0eb34c15030d4e2069ac", [:mix], [], "hexpm", "9713b3f238d07043583a94296cc4bbdceacd3b3a6c74667f4df13971e7866ec8"}, 13 | "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.3", "33914a5d47345c7bde056054bca05c0bbbda5ce94a3ee734cb20a4d5d361e20d", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "157d93a5a7c79b836d9b7b5d55c7dd021435139d6160112f0e630f0686b8ea88"}, 14 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, 15 | "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, 16 | "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, 17 | "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, 18 | "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, 19 | "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, 20 | "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, 21 | "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, 22 | } 23 | -------------------------------------------------------------------------------- /test/live_elements_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTest do 2 | use ExUnit.Case 3 | doctest LiveElements 4 | 5 | test "greets the world" do 6 | assert LiveElements.hello() == :world 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /testbed/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | plugins: [Phoenix.LiveView.HTMLFormatter], 4 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /testbed/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Temporary files, for example, from tests. 23 | /tmp/ 24 | 25 | # Ignore package tarball (built via "mix hex.build"). 26 | live_elements_testbed-*.tar 27 | 28 | # Ignore assets that are produced by build tools. 29 | /priv/static/assets/ 30 | 31 | # Ignore digested assets cache. 32 | /priv/static/cache_manifest.json 33 | 34 | # In case you use Node.js/npm, you want to ignore these. 35 | npm-debug.log 36 | /assets/node_modules/ 37 | 38 | -------------------------------------------------------------------------------- /testbed/.tool-versions: -------------------------------------------------------------------------------- 1 | elixir 1.16.3-otp-26 2 | erlang 26.2.1 3 | -------------------------------------------------------------------------------- /testbed/README.md: -------------------------------------------------------------------------------- 1 | # LiveElementsTestbed 2 | 3 | To start your Phoenix server: 4 | 5 | * Run `mix setup` to install and setup dependencies 6 | * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` 7 | 8 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 9 | 10 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 11 | 12 | ## Learn more 13 | 14 | * Official website: https://www.phoenixframework.org/ 15 | * Guides: https://hexdocs.pm/phoenix/overview.html 16 | * Docs: https://hexdocs.pm/phoenix 17 | * Forum: https://elixirforum.com/c/phoenix-forum 18 | * Source: https://github.com/phoenixframework/phoenix 19 | -------------------------------------------------------------------------------- /testbed/assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss/base"; 2 | @import "tailwindcss/components"; 3 | @import "tailwindcss/utilities"; 4 | 5 | .btn { 6 | @apply font-bold py-2 px-4 rounded; 7 | } 8 | .btn-blue { 9 | @apply bg-blue-500 text-white; 10 | } 11 | .btn-blue:hover { 12 | @apply bg-blue-700; 13 | } 14 | 15 | /* This file is for your main application CSS */ 16 | -------------------------------------------------------------------------------- /testbed/assets/custom-elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "readme": "", 4 | "modules": [ 5 | { 6 | "kind": "javascript-module", 7 | "path": "js/app.js", 8 | "declarations": [], 9 | "exports": [] 10 | }, 11 | { 12 | "kind": "javascript-module", 13 | "path": "js/pie-chart.ts", 14 | "declarations": [ 15 | { 16 | "kind": "class", 17 | "description": "", 18 | "name": "PieChartElement", 19 | "members": [ 20 | { 21 | "kind": "field", 22 | "name": "chartData", 23 | "type": { 24 | "text": "{}" 25 | }, 26 | "attribute": "chart-data" 27 | }, 28 | { 29 | "kind": "field", 30 | "name": "label", 31 | "type": { 32 | "text": "string" 33 | }, 34 | "attribute": "label" 35 | } 36 | ], 37 | "attributes": [ 38 | { 39 | "name": "chart-data", 40 | "type": { 41 | "text": "{}" 42 | }, 43 | "fieldName": "chartData" 44 | }, 45 | { 46 | "name": "label", 47 | "type": { 48 | "text": "string" 49 | }, 50 | "fieldName": "label" 51 | } 52 | ], 53 | "superclass": { 54 | "name": "LitElement", 55 | "package": "lit" 56 | }, 57 | "tagName": "pie-chart", 58 | "customElement": true 59 | } 60 | ], 61 | "exports": [ 62 | { 63 | "kind": "js", 64 | "name": "PieChartElement", 65 | "declaration": { 66 | "name": "PieChartElement", 67 | "module": "js/pie-chart.ts" 68 | } 69 | }, 70 | { 71 | "kind": "custom-element-definition", 72 | "name": "pie-chart", 73 | "declaration": { 74 | "name": "PieChartElement", 75 | "module": "js/pie-chart.ts" 76 | } 77 | } 78 | ] 79 | }, 80 | { 81 | "kind": "javascript-module", 82 | "path": "js/todo-list.ts", 83 | "declarations": [ 84 | { 85 | "kind": "class", 86 | "description": "", 87 | "name": "TodoListElement", 88 | "members": [ 89 | { 90 | "kind": "field", 91 | "name": "todos", 92 | "type": { 93 | "text": "Array" 94 | }, 95 | "default": "[]", 96 | "attribute": "todos" 97 | }, 98 | { 99 | "kind": "field", 100 | "name": "todoInput", 101 | "type": { 102 | "text": "HTMLInputElement | undefined" 103 | } 104 | }, 105 | { 106 | "kind": "method", 107 | "name": "addTodo", 108 | "parameters": [ 109 | { 110 | "name": "_event", 111 | "type": { 112 | "text": "Event" 113 | } 114 | } 115 | ] 116 | } 117 | ], 118 | "events": [ 119 | { 120 | "name": "add_todo", 121 | "type": { 122 | "text": "CustomEvent" 123 | } 124 | } 125 | ], 126 | "attributes": [ 127 | { 128 | "name": "todos", 129 | "type": { 130 | "text": "Array" 131 | }, 132 | "default": "[]", 133 | "fieldName": "todos" 134 | } 135 | ], 136 | "superclass": { 137 | "name": "LitElement", 138 | "package": "lit" 139 | }, 140 | "tagName": "todo-list", 141 | "customElement": true 142 | } 143 | ], 144 | "exports": [ 145 | { 146 | "kind": "js", 147 | "name": "TodoListElement", 148 | "declaration": { 149 | "name": "TodoListElement", 150 | "module": "js/todo-list.ts" 151 | } 152 | }, 153 | { 154 | "kind": "custom-element-definition", 155 | "name": "todo-list", 156 | "declaration": { 157 | "name": "TodoListElement", 158 | "module": "js/todo-list.ts" 159 | } 160 | } 161 | ] 162 | }, 163 | { 164 | "kind": "javascript-module", 165 | "path": "vendor/topbar.js", 166 | "declarations": [], 167 | "exports": [] 168 | } 169 | ] 170 | } 171 | -------------------------------------------------------------------------------- /testbed/assets/js/app.js: -------------------------------------------------------------------------------- 1 | // If you want to use Phoenix channels, run `mix help phx.gen.channel` 2 | // to get started and then uncomment the line below. 3 | // import "./user_socket.js" 4 | 5 | // You can include dependencies in two ways. 6 | // 7 | // The simplest option is to put them in assets/vendor and 8 | // import them using relative paths: 9 | // 10 | // import "../vendor/some-package.js" 11 | // 12 | // Alternatively, you can `npm install some-package --prefix assets` and import 13 | // them using a path starting with the package name: 14 | // 15 | // import "some-package" 16 | // 17 | 18 | // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. 19 | import "phoenix_html" 20 | // Establish Phoenix Socket and LiveView configuration. 21 | import {Socket} from "phoenix" 22 | import {LiveSocket} from "phoenix_live_view" 23 | import topbar from "../vendor/topbar" 24 | import PhoenixCustomEventHook from 'phoenix-custom-event-hook'; 25 | 26 | let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") 27 | let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: {PhoenixCustomEventHook}}) 28 | 29 | // Show progress bar on live navigation and form submits 30 | topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) 31 | window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) 32 | window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) 33 | 34 | // connect if there are any LiveViews on the page 35 | liveSocket.connect() 36 | 37 | // expose liveSocket on window for web console debug logs and latency simulation: 38 | // >> liveSocket.enableDebug() 39 | // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session 40 | // >> liveSocket.disableLatencySim() 41 | window.liveSocket = liveSocket 42 | 43 | import './todo-list'; 44 | import './simple-guy'; 45 | import './pie-chart'; 46 | import './foo-input'; 47 | import '@carbon/web-components/es/components/data-table/index.js'; 48 | 49 | document.addEventListener("DOMContentLoaded", (e) =>{ 50 | const table = document.querySelector("cds-table"); 51 | if (table) { 52 | console.log('found a table!!'); 53 | table.customSortRow = (_rhs, _lhs, _collator) => { 54 | return 0; 55 | } 56 | } 57 | }); -------------------------------------------------------------------------------- /testbed/assets/js/foo-input.js: -------------------------------------------------------------------------------- 1 | class FooInput extends HTMLElement { 2 | static formAssociated = true; 3 | 4 | constructor() { 5 | super(); 6 | this.elementInternals = this.attachInternals(); 7 | this.attachShadow({mode: 'open'}); 8 | this.shadowRoot.innerHTML = `set ${this.getAttribute('name')} to foo`; 9 | this.addEventListener('click', () => { 10 | this.elementInternals.setFormValue('foo'); 11 | new FormData(this.elementInternals.form).forEach(console.log); 12 | }) 13 | } 14 | } 15 | 16 | customElements.define('foo-input', FooInput); -------------------------------------------------------------------------------- /testbed/assets/js/pie-chart.ts: -------------------------------------------------------------------------------- 1 | import { html, css, LitElement } from 'lit'; 2 | import { customElement, property, query } from 'lit/decorators.js'; 3 | import Chart from 'chart.js/auto'; 4 | 5 | @customElement('pie-chart') 6 | export class PieChartElement extends LitElement { 7 | @property({ attribute: 'chart-data', type: Object }) 8 | chartData: {}; 9 | 10 | @property() 11 | label: string; 12 | 13 | chart: Chart<"pie">; 14 | 15 | @query('#chart') 16 | chartContainer: HTMLCanvasElement; 17 | 18 | render() { 19 | return html` 20 | 21 | `; 22 | } 23 | 24 | firstUpdated() { 25 | this.chart = new Chart<"pie">( 26 | this.chartContainer, 27 | { 28 | type: 'pie', 29 | data: { 30 | labels: Object.keys(this.chartData), 31 | datasets: [ 32 | { 33 | label: 'Pies I have eaten', 34 | data: Object.values(this.chartData) 35 | } 36 | ] 37 | } 38 | } 39 | ); 40 | } 41 | 42 | updated() { 43 | this.chart.data.datasets[0].data = Object.values(this.chartData); 44 | this.chart.update(); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /testbed/assets/js/simple-guy.ts: -------------------------------------------------------------------------------- 1 | import { html, css, LitElement } from 'lit' 2 | import { customElement, property, query } from 'lit/decorators.js' 3 | 4 | @customElement('simple-guy') 5 | export class SimpleGuyElement extends LitElement { 6 | @property({attribute: 'first-name'}) 7 | name: string = ''; 8 | 9 | @property() 10 | age: number = 0; 11 | 12 | render() { 13 | return html`
My name is ${this.name} and I am ${this.age} years old.
` 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /testbed/assets/js/todo-list.ts: -------------------------------------------------------------------------------- 1 | import { html, css, LitElement } from 'lit' 2 | import { customElement, property, query } from 'lit/decorators.js' 3 | 4 | @customElement('todo-list') 5 | export class TodoListElement extends LitElement { 6 | 7 | @property({type: Array}) 8 | todos: Array = []; 9 | 10 | @query("input[name='todo']") 11 | todoInput: HTMLInputElement | undefined; 12 | 13 | render() { 14 | return html` 15 |
16 | This is my todo list 17 |
    18 | ${this.todos?.map(todo => html`
  • ${todo}
  • `)} 19 |
20 |
21 |
22 | 23 | 24 |
25 | ` 26 | } 27 | 28 | addTodo(_event : Event) { 29 | this.dispatchEvent(new CustomEvent('add_todo', {detail: {todo: this.todoInput!.value}})); 30 | this.todoInput!.value = ''; 31 | } 32 | } -------------------------------------------------------------------------------- /testbed/assets/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "@carbon/web-components": "^1.24.0", 9 | "chart.js": "^4.2.1", 10 | "lit": "^2.6.1", 11 | "phoenix-custom-event-hook": "^0.0.6" 12 | }, 13 | "devDependencies": { 14 | "@custom-elements-manifest/analyzer": "^0.6.8" 15 | } 16 | }, 17 | "node_modules/@babel/runtime": { 18 | "version": "7.21.0", 19 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", 20 | "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", 21 | "dependencies": { 22 | "regenerator-runtime": "^0.13.11" 23 | }, 24 | "engines": { 25 | "node": ">=6.9.0" 26 | } 27 | }, 28 | "node_modules/@carbon/telemetry": { 29 | "version": "0.1.0", 30 | "resolved": "https://registry.npmjs.org/@carbon/telemetry/-/telemetry-0.1.0.tgz", 31 | "integrity": "sha512-kNWt0bkgPwGW0i5h7HFuljbKRXPvIhsKbB+1tEURAYLXoJg9iJLF1eGvWN5iVoFCS2zje4GR3OGOsvvKVe7Hlg==", 32 | "bin": { 33 | "carbon-telemetry": "bin/carbon-telemetry.js" 34 | } 35 | }, 36 | "node_modules/@carbon/web-components": { 37 | "version": "1.41.0", 38 | "resolved": "https://registry.npmjs.org/@carbon/web-components/-/web-components-1.41.0.tgz", 39 | "integrity": "sha512-JTaoxYplEB8dSLOYboiKDwZOGsdGG76Nn07Ufwa7R9mvNKcCpXHj9C4L7VN8PnOgs40hAaflTGW4jmXmdpYn5A==", 40 | "hasInstallScript": true, 41 | "dependencies": { 42 | "@babel/runtime": "^7.16.3", 43 | "@ibm/telemetry-js": "^1.3.0", 44 | "carbon-components": "10.58.3", 45 | "flatpickr": "4.6.13", 46 | "lit-element": "^2.5.1", 47 | "lit-html": "^1.4.1", 48 | "lodash-es": "^4.17.21" 49 | } 50 | }, 51 | "node_modules/@carbon/web-components/node_modules/lit-element": { 52 | "version": "2.5.1", 53 | "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz", 54 | "integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==", 55 | "dependencies": { 56 | "lit-html": "^1.1.1" 57 | } 58 | }, 59 | "node_modules/@carbon/web-components/node_modules/lit-html": { 60 | "version": "1.4.1", 61 | "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", 62 | "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==" 63 | }, 64 | "node_modules/@custom-elements-manifest/analyzer": { 65 | "version": "0.6.8", 66 | "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.6.8.tgz", 67 | "integrity": "sha512-Wy5CiMUq9njT6vbeEPi7Zjm7OcVmL+5zyJXUam5TZe13QVnsn/m3VaJx8aqMecTp1m/pFNCL4QGJpYV/oZYIkg==", 68 | "dev": true, 69 | "dependencies": { 70 | "@custom-elements-manifest/find-dependencies": "^0.0.5", 71 | "@github/catalyst": "^1.6.0", 72 | "@web/config-loader": "0.1.3", 73 | "chokidar": "3.5.2", 74 | "command-line-args": "5.1.2", 75 | "comment-parser": "1.2.4", 76 | "custom-elements-manifest": "1.0.0", 77 | "debounce": "1.2.1", 78 | "globby": "11.0.4", 79 | "typescript": "~4.3.2" 80 | }, 81 | "bin": { 82 | "cem": "cem.js", 83 | "custom-elements-manifest": "cem.js" 84 | } 85 | }, 86 | "node_modules/@custom-elements-manifest/find-dependencies": { 87 | "version": "0.0.5", 88 | "resolved": "https://registry.npmjs.org/@custom-elements-manifest/find-dependencies/-/find-dependencies-0.0.5.tgz", 89 | "integrity": "sha512-fKIMMZCDFSoL2ySUoz8knWgpV4jpb0lUXgLOvdZQMQFHxgxz1PqOJpUIypwvEVyKk3nEHRY4f10gNol02HjeCg==", 90 | "dev": true, 91 | "dependencies": { 92 | "es-module-lexer": "^0.9.3" 93 | } 94 | }, 95 | "node_modules/@github/catalyst": { 96 | "version": "1.6.0", 97 | "resolved": "https://registry.npmjs.org/@github/catalyst/-/catalyst-1.6.0.tgz", 98 | "integrity": "sha512-u8A+DameixqpeyHzvnJWTGj+wfiskQOYHzSiJscCWVfMkIT3rxnbHMtGh3lMthaRY21nbUOK71WcsCnCrXhBJQ==", 99 | "dev": true 100 | }, 101 | "node_modules/@ibm/telemetry-js": { 102 | "version": "1.6.0", 103 | "resolved": "https://registry.npmjs.org/@ibm/telemetry-js/-/telemetry-js-1.6.0.tgz", 104 | "integrity": "sha512-XXdsXhoZwKaJ1/24w/uCqx/YeDQeTu8SAl+7bHQxn62yiFUQ6/D20HhpQVdlG9/Jpv3nYJHFiDH0vGkQbXNFWg==", 105 | "bin": { 106 | "ibmtelemetry": "dist/collect.js" 107 | } 108 | }, 109 | "node_modules/@kurkle/color": { 110 | "version": "0.3.2", 111 | "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", 112 | "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" 113 | }, 114 | "node_modules/@lit-labs/ssr-dom-shim": { 115 | "version": "1.2.0", 116 | "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", 117 | "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" 118 | }, 119 | "node_modules/@lit/reactive-element": { 120 | "version": "1.6.3", 121 | "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", 122 | "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", 123 | "dependencies": { 124 | "@lit-labs/ssr-dom-shim": "^1.0.0" 125 | } 126 | }, 127 | "node_modules/@nodelib/fs.scandir": { 128 | "version": "2.1.5", 129 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 130 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 131 | "dev": true, 132 | "dependencies": { 133 | "@nodelib/fs.stat": "2.0.5", 134 | "run-parallel": "^1.1.9" 135 | }, 136 | "engines": { 137 | "node": ">= 8" 138 | } 139 | }, 140 | "node_modules/@nodelib/fs.stat": { 141 | "version": "2.0.5", 142 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 143 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 144 | "dev": true, 145 | "engines": { 146 | "node": ">= 8" 147 | } 148 | }, 149 | "node_modules/@nodelib/fs.walk": { 150 | "version": "1.2.8", 151 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 152 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 153 | "dev": true, 154 | "dependencies": { 155 | "@nodelib/fs.scandir": "2.1.5", 156 | "fastq": "^1.6.0" 157 | }, 158 | "engines": { 159 | "node": ">= 8" 160 | } 161 | }, 162 | "node_modules/@types/trusted-types": { 163 | "version": "2.0.7", 164 | "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 165 | "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" 166 | }, 167 | "node_modules/@web/config-loader": { 168 | "version": "0.1.3", 169 | "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.1.3.tgz", 170 | "integrity": "sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==", 171 | "dev": true, 172 | "dependencies": { 173 | "semver": "^7.3.4" 174 | }, 175 | "engines": { 176 | "node": ">=10.0.0" 177 | } 178 | }, 179 | "node_modules/anymatch": { 180 | "version": "3.1.3", 181 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 182 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 183 | "dev": true, 184 | "dependencies": { 185 | "normalize-path": "^3.0.0", 186 | "picomatch": "^2.0.4" 187 | }, 188 | "engines": { 189 | "node": ">= 8" 190 | } 191 | }, 192 | "node_modules/array-back": { 193 | "version": "6.2.2", 194 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", 195 | "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", 196 | "dev": true, 197 | "engines": { 198 | "node": ">=12.17" 199 | } 200 | }, 201 | "node_modules/array-union": { 202 | "version": "2.1.0", 203 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 204 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 205 | "dev": true, 206 | "engines": { 207 | "node": ">=8" 208 | } 209 | }, 210 | "node_modules/binary-extensions": { 211 | "version": "2.2.0", 212 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 213 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 214 | "dev": true, 215 | "engines": { 216 | "node": ">=8" 217 | } 218 | }, 219 | "node_modules/braces": { 220 | "version": "3.0.2", 221 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 222 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 223 | "dev": true, 224 | "dependencies": { 225 | "fill-range": "^7.0.1" 226 | }, 227 | "engines": { 228 | "node": ">=8" 229 | } 230 | }, 231 | "node_modules/carbon-components": { 232 | "version": "10.58.3", 233 | "resolved": "https://registry.npmjs.org/carbon-components/-/carbon-components-10.58.3.tgz", 234 | "integrity": "sha512-RjTnrWCGStsIZ7nErw97AZI9sQWxQ8oIgo3QMdV0FWFcpTOECA4I9Dy4WPpRRdSMBcQpLetTxqjDGourM4u8Tw==", 235 | "hasInstallScript": true, 236 | "dependencies": { 237 | "@carbon/telemetry": "0.1.0", 238 | "flatpickr": "4.6.1", 239 | "lodash.debounce": "^4.0.8", 240 | "warning": "^3.0.0" 241 | } 242 | }, 243 | "node_modules/carbon-components/node_modules/flatpickr": { 244 | "version": "4.6.1", 245 | "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.1.tgz", 246 | "integrity": "sha512-3ULSxbXmcMIRzer/2jLNweoqHpwDvsjEawO2FUd9UFR8uPwLM+LruZcPDpuZStcEgbQKhuFOfXo4nYdGladSNw==" 247 | }, 248 | "node_modules/chart.js": { 249 | "version": "4.2.1", 250 | "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", 251 | "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", 252 | "dependencies": { 253 | "@kurkle/color": "^0.3.0" 254 | }, 255 | "engines": { 256 | "pnpm": "^7.0.0" 257 | } 258 | }, 259 | "node_modules/chokidar": { 260 | "version": "3.5.2", 261 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", 262 | "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", 263 | "dev": true, 264 | "dependencies": { 265 | "anymatch": "~3.1.2", 266 | "braces": "~3.0.2", 267 | "glob-parent": "~5.1.2", 268 | "is-binary-path": "~2.1.0", 269 | "is-glob": "~4.0.1", 270 | "normalize-path": "~3.0.0", 271 | "readdirp": "~3.6.0" 272 | }, 273 | "engines": { 274 | "node": ">= 8.10.0" 275 | }, 276 | "optionalDependencies": { 277 | "fsevents": "~2.3.2" 278 | } 279 | }, 280 | "node_modules/command-line-args": { 281 | "version": "5.1.2", 282 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", 283 | "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", 284 | "dev": true, 285 | "dependencies": { 286 | "array-back": "^6.1.2", 287 | "find-replace": "^3.0.0", 288 | "lodash.camelcase": "^4.3.0", 289 | "typical": "^4.0.0" 290 | }, 291 | "engines": { 292 | "node": ">=4.0.0" 293 | } 294 | }, 295 | "node_modules/comment-parser": { 296 | "version": "1.2.4", 297 | "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", 298 | "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", 299 | "dev": true, 300 | "engines": { 301 | "node": ">= 12.0.0" 302 | } 303 | }, 304 | "node_modules/custom-elements-manifest": { 305 | "version": "1.0.0", 306 | "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", 307 | "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", 308 | "dev": true 309 | }, 310 | "node_modules/debounce": { 311 | "version": "1.2.1", 312 | "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", 313 | "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", 314 | "dev": true 315 | }, 316 | "node_modules/dir-glob": { 317 | "version": "3.0.1", 318 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 319 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 320 | "dev": true, 321 | "dependencies": { 322 | "path-type": "^4.0.0" 323 | }, 324 | "engines": { 325 | "node": ">=8" 326 | } 327 | }, 328 | "node_modules/es-module-lexer": { 329 | "version": "0.9.3", 330 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", 331 | "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", 332 | "dev": true 333 | }, 334 | "node_modules/fast-glob": { 335 | "version": "3.2.12", 336 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 337 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 338 | "dev": true, 339 | "dependencies": { 340 | "@nodelib/fs.stat": "^2.0.2", 341 | "@nodelib/fs.walk": "^1.2.3", 342 | "glob-parent": "^5.1.2", 343 | "merge2": "^1.3.0", 344 | "micromatch": "^4.0.4" 345 | }, 346 | "engines": { 347 | "node": ">=8.6.0" 348 | } 349 | }, 350 | "node_modules/fastq": { 351 | "version": "1.15.0", 352 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 353 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 354 | "dev": true, 355 | "dependencies": { 356 | "reusify": "^1.0.4" 357 | } 358 | }, 359 | "node_modules/fill-range": { 360 | "version": "7.0.1", 361 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 362 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 363 | "dev": true, 364 | "dependencies": { 365 | "to-regex-range": "^5.0.1" 366 | }, 367 | "engines": { 368 | "node": ">=8" 369 | } 370 | }, 371 | "node_modules/find-replace": { 372 | "version": "3.0.0", 373 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 374 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 375 | "dev": true, 376 | "dependencies": { 377 | "array-back": "^3.0.1" 378 | }, 379 | "engines": { 380 | "node": ">=4.0.0" 381 | } 382 | }, 383 | "node_modules/find-replace/node_modules/array-back": { 384 | "version": "3.1.0", 385 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 386 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", 387 | "dev": true, 388 | "engines": { 389 | "node": ">=6" 390 | } 391 | }, 392 | "node_modules/flatpickr": { 393 | "version": "4.6.13", 394 | "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", 395 | "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" 396 | }, 397 | "node_modules/fsevents": { 398 | "version": "2.3.2", 399 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 400 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 401 | "dev": true, 402 | "hasInstallScript": true, 403 | "optional": true, 404 | "os": [ 405 | "darwin" 406 | ], 407 | "engines": { 408 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 409 | } 410 | }, 411 | "node_modules/glob-parent": { 412 | "version": "5.1.2", 413 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 414 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 415 | "dev": true, 416 | "dependencies": { 417 | "is-glob": "^4.0.1" 418 | }, 419 | "engines": { 420 | "node": ">= 6" 421 | } 422 | }, 423 | "node_modules/globby": { 424 | "version": "11.0.4", 425 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", 426 | "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", 427 | "dev": true, 428 | "dependencies": { 429 | "array-union": "^2.1.0", 430 | "dir-glob": "^3.0.1", 431 | "fast-glob": "^3.1.1", 432 | "ignore": "^5.1.4", 433 | "merge2": "^1.3.0", 434 | "slash": "^3.0.0" 435 | }, 436 | "engines": { 437 | "node": ">=10" 438 | }, 439 | "funding": { 440 | "url": "https://github.com/sponsors/sindresorhus" 441 | } 442 | }, 443 | "node_modules/ignore": { 444 | "version": "5.2.4", 445 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 446 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", 447 | "dev": true, 448 | "engines": { 449 | "node": ">= 4" 450 | } 451 | }, 452 | "node_modules/is-binary-path": { 453 | "version": "2.1.0", 454 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 455 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 456 | "dev": true, 457 | "dependencies": { 458 | "binary-extensions": "^2.0.0" 459 | }, 460 | "engines": { 461 | "node": ">=8" 462 | } 463 | }, 464 | "node_modules/is-extglob": { 465 | "version": "2.1.1", 466 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 467 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 468 | "dev": true, 469 | "engines": { 470 | "node": ">=0.10.0" 471 | } 472 | }, 473 | "node_modules/is-glob": { 474 | "version": "4.0.3", 475 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 476 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 477 | "dev": true, 478 | "dependencies": { 479 | "is-extglob": "^2.1.1" 480 | }, 481 | "engines": { 482 | "node": ">=0.10.0" 483 | } 484 | }, 485 | "node_modules/is-number": { 486 | "version": "7.0.0", 487 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 488 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 489 | "dev": true, 490 | "engines": { 491 | "node": ">=0.12.0" 492 | } 493 | }, 494 | "node_modules/js-tokens": { 495 | "version": "4.0.0", 496 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 497 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 498 | }, 499 | "node_modules/lit": { 500 | "version": "2.8.0", 501 | "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", 502 | "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", 503 | "dependencies": { 504 | "@lit/reactive-element": "^1.6.0", 505 | "lit-element": "^3.3.0", 506 | "lit-html": "^2.8.0" 507 | } 508 | }, 509 | "node_modules/lit-element": { 510 | "version": "3.3.3", 511 | "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", 512 | "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", 513 | "dependencies": { 514 | "@lit-labs/ssr-dom-shim": "^1.1.0", 515 | "@lit/reactive-element": "^1.3.0", 516 | "lit-html": "^2.8.0" 517 | } 518 | }, 519 | "node_modules/lit-html": { 520 | "version": "2.8.0", 521 | "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", 522 | "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", 523 | "dependencies": { 524 | "@types/trusted-types": "^2.0.2" 525 | } 526 | }, 527 | "node_modules/lodash-es": { 528 | "version": "4.17.21", 529 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 530 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 531 | }, 532 | "node_modules/lodash.camelcase": { 533 | "version": "4.3.0", 534 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 535 | "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", 536 | "dev": true 537 | }, 538 | "node_modules/lodash.debounce": { 539 | "version": "4.0.8", 540 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 541 | "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" 542 | }, 543 | "node_modules/loose-envify": { 544 | "version": "1.4.0", 545 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 546 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 547 | "dependencies": { 548 | "js-tokens": "^3.0.0 || ^4.0.0" 549 | }, 550 | "bin": { 551 | "loose-envify": "cli.js" 552 | } 553 | }, 554 | "node_modules/lru-cache": { 555 | "version": "6.0.0", 556 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 557 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 558 | "dev": true, 559 | "dependencies": { 560 | "yallist": "^4.0.0" 561 | }, 562 | "engines": { 563 | "node": ">=10" 564 | } 565 | }, 566 | "node_modules/merge2": { 567 | "version": "1.4.1", 568 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 569 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 570 | "dev": true, 571 | "engines": { 572 | "node": ">= 8" 573 | } 574 | }, 575 | "node_modules/micromatch": { 576 | "version": "4.0.5", 577 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 578 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 579 | "dev": true, 580 | "dependencies": { 581 | "braces": "^3.0.2", 582 | "picomatch": "^2.3.1" 583 | }, 584 | "engines": { 585 | "node": ">=8.6" 586 | } 587 | }, 588 | "node_modules/ms": { 589 | "version": "2.1.3", 590 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 591 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 592 | }, 593 | "node_modules/normalize-path": { 594 | "version": "3.0.0", 595 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 596 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 597 | "dev": true, 598 | "engines": { 599 | "node": ">=0.10.0" 600 | } 601 | }, 602 | "node_modules/path-type": { 603 | "version": "4.0.0", 604 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 605 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 606 | "dev": true, 607 | "engines": { 608 | "node": ">=8" 609 | } 610 | }, 611 | "node_modules/phoenix-custom-event-hook": { 612 | "version": "0.0.6", 613 | "resolved": "https://registry.npmjs.org/phoenix-custom-event-hook/-/phoenix-custom-event-hook-0.0.6.tgz", 614 | "integrity": "sha512-6rPab4rL3UwhV1En4ACvwbyG7Y3o0/I2yJDpI6BgHdt8ie/LaMG+OL3MjYt67kGybWYGC/EhyXdUsZGng3dqtA==", 615 | "dependencies": { 616 | "ms": "^2.0.0" 617 | } 618 | }, 619 | "node_modules/picomatch": { 620 | "version": "2.3.1", 621 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 622 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 623 | "dev": true, 624 | "engines": { 625 | "node": ">=8.6" 626 | }, 627 | "funding": { 628 | "url": "https://github.com/sponsors/jonschlinkert" 629 | } 630 | }, 631 | "node_modules/queue-microtask": { 632 | "version": "1.2.3", 633 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 634 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 635 | "dev": true, 636 | "funding": [ 637 | { 638 | "type": "github", 639 | "url": "https://github.com/sponsors/feross" 640 | }, 641 | { 642 | "type": "patreon", 643 | "url": "https://www.patreon.com/feross" 644 | }, 645 | { 646 | "type": "consulting", 647 | "url": "https://feross.org/support" 648 | } 649 | ] 650 | }, 651 | "node_modules/readdirp": { 652 | "version": "3.6.0", 653 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 654 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 655 | "dev": true, 656 | "dependencies": { 657 | "picomatch": "^2.2.1" 658 | }, 659 | "engines": { 660 | "node": ">=8.10.0" 661 | } 662 | }, 663 | "node_modules/regenerator-runtime": { 664 | "version": "0.13.11", 665 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", 666 | "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" 667 | }, 668 | "node_modules/reusify": { 669 | "version": "1.0.4", 670 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 671 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 672 | "dev": true, 673 | "engines": { 674 | "iojs": ">=1.0.0", 675 | "node": ">=0.10.0" 676 | } 677 | }, 678 | "node_modules/run-parallel": { 679 | "version": "1.2.0", 680 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 681 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 682 | "dev": true, 683 | "funding": [ 684 | { 685 | "type": "github", 686 | "url": "https://github.com/sponsors/feross" 687 | }, 688 | { 689 | "type": "patreon", 690 | "url": "https://www.patreon.com/feross" 691 | }, 692 | { 693 | "type": "consulting", 694 | "url": "https://feross.org/support" 695 | } 696 | ], 697 | "dependencies": { 698 | "queue-microtask": "^1.2.2" 699 | } 700 | }, 701 | "node_modules/semver": { 702 | "version": "7.3.8", 703 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 704 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 705 | "dev": true, 706 | "dependencies": { 707 | "lru-cache": "^6.0.0" 708 | }, 709 | "bin": { 710 | "semver": "bin/semver.js" 711 | }, 712 | "engines": { 713 | "node": ">=10" 714 | } 715 | }, 716 | "node_modules/slash": { 717 | "version": "3.0.0", 718 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 719 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 720 | "dev": true, 721 | "engines": { 722 | "node": ">=8" 723 | } 724 | }, 725 | "node_modules/to-regex-range": { 726 | "version": "5.0.1", 727 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 728 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 729 | "dev": true, 730 | "dependencies": { 731 | "is-number": "^7.0.0" 732 | }, 733 | "engines": { 734 | "node": ">=8.0" 735 | } 736 | }, 737 | "node_modules/typescript": { 738 | "version": "4.3.5", 739 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 740 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 741 | "dev": true, 742 | "bin": { 743 | "tsc": "bin/tsc", 744 | "tsserver": "bin/tsserver" 745 | }, 746 | "engines": { 747 | "node": ">=4.2.0" 748 | } 749 | }, 750 | "node_modules/typical": { 751 | "version": "4.0.0", 752 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 753 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", 754 | "dev": true, 755 | "engines": { 756 | "node": ">=8" 757 | } 758 | }, 759 | "node_modules/warning": { 760 | "version": "3.0.0", 761 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 762 | "integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==", 763 | "dependencies": { 764 | "loose-envify": "^1.0.0" 765 | } 766 | }, 767 | "node_modules/yallist": { 768 | "version": "4.0.0", 769 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 770 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 771 | "dev": true 772 | } 773 | }, 774 | "dependencies": { 775 | "@babel/runtime": { 776 | "version": "7.21.0", 777 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", 778 | "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", 779 | "requires": { 780 | "regenerator-runtime": "^0.13.11" 781 | } 782 | }, 783 | "@carbon/telemetry": { 784 | "version": "0.1.0", 785 | "resolved": "https://registry.npmjs.org/@carbon/telemetry/-/telemetry-0.1.0.tgz", 786 | "integrity": "sha512-kNWt0bkgPwGW0i5h7HFuljbKRXPvIhsKbB+1tEURAYLXoJg9iJLF1eGvWN5iVoFCS2zje4GR3OGOsvvKVe7Hlg==" 787 | }, 788 | "@carbon/web-components": { 789 | "version": "1.41.0", 790 | "resolved": "https://registry.npmjs.org/@carbon/web-components/-/web-components-1.41.0.tgz", 791 | "integrity": "sha512-JTaoxYplEB8dSLOYboiKDwZOGsdGG76Nn07Ufwa7R9mvNKcCpXHj9C4L7VN8PnOgs40hAaflTGW4jmXmdpYn5A==", 792 | "requires": { 793 | "@babel/runtime": "^7.16.3", 794 | "@ibm/telemetry-js": "^1.3.0", 795 | "carbon-components": "10.58.3", 796 | "flatpickr": "4.6.13", 797 | "lit-element": "^2.5.1", 798 | "lit-html": "^1.4.1", 799 | "lodash-es": "^4.17.21" 800 | }, 801 | "dependencies": { 802 | "lit-element": { 803 | "version": "2.5.1", 804 | "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-2.5.1.tgz", 805 | "integrity": "sha512-ogu7PiJTA33bEK0xGu1dmaX5vhcRjBXCFexPja0e7P7jqLhTpNKYRPmE+GmiCaRVAbiQKGkUgkh/i6+bh++dPQ==", 806 | "requires": { 807 | "lit-html": "^1.1.1" 808 | } 809 | }, 810 | "lit-html": { 811 | "version": "1.4.1", 812 | "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-1.4.1.tgz", 813 | "integrity": "sha512-B9btcSgPYb1q4oSOb/PrOT6Z/H+r6xuNzfH4lFli/AWhYwdtrgQkQWBbIc6mdnf6E2IL3gDXdkkqNktpU0OZQA==" 814 | } 815 | } 816 | }, 817 | "@custom-elements-manifest/analyzer": { 818 | "version": "0.6.8", 819 | "resolved": "https://registry.npmjs.org/@custom-elements-manifest/analyzer/-/analyzer-0.6.8.tgz", 820 | "integrity": "sha512-Wy5CiMUq9njT6vbeEPi7Zjm7OcVmL+5zyJXUam5TZe13QVnsn/m3VaJx8aqMecTp1m/pFNCL4QGJpYV/oZYIkg==", 821 | "dev": true, 822 | "requires": { 823 | "@custom-elements-manifest/find-dependencies": "^0.0.5", 824 | "@github/catalyst": "^1.6.0", 825 | "@web/config-loader": "0.1.3", 826 | "chokidar": "3.5.2", 827 | "command-line-args": "5.1.2", 828 | "comment-parser": "1.2.4", 829 | "custom-elements-manifest": "1.0.0", 830 | "debounce": "1.2.1", 831 | "globby": "11.0.4", 832 | "typescript": "~4.3.2" 833 | } 834 | }, 835 | "@custom-elements-manifest/find-dependencies": { 836 | "version": "0.0.5", 837 | "resolved": "https://registry.npmjs.org/@custom-elements-manifest/find-dependencies/-/find-dependencies-0.0.5.tgz", 838 | "integrity": "sha512-fKIMMZCDFSoL2ySUoz8knWgpV4jpb0lUXgLOvdZQMQFHxgxz1PqOJpUIypwvEVyKk3nEHRY4f10gNol02HjeCg==", 839 | "dev": true, 840 | "requires": { 841 | "es-module-lexer": "^0.9.3" 842 | } 843 | }, 844 | "@github/catalyst": { 845 | "version": "1.6.0", 846 | "resolved": "https://registry.npmjs.org/@github/catalyst/-/catalyst-1.6.0.tgz", 847 | "integrity": "sha512-u8A+DameixqpeyHzvnJWTGj+wfiskQOYHzSiJscCWVfMkIT3rxnbHMtGh3lMthaRY21nbUOK71WcsCnCrXhBJQ==", 848 | "dev": true 849 | }, 850 | "@ibm/telemetry-js": { 851 | "version": "1.6.0", 852 | "resolved": "https://registry.npmjs.org/@ibm/telemetry-js/-/telemetry-js-1.6.0.tgz", 853 | "integrity": "sha512-XXdsXhoZwKaJ1/24w/uCqx/YeDQeTu8SAl+7bHQxn62yiFUQ6/D20HhpQVdlG9/Jpv3nYJHFiDH0vGkQbXNFWg==" 854 | }, 855 | "@kurkle/color": { 856 | "version": "0.3.2", 857 | "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", 858 | "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" 859 | }, 860 | "@lit-labs/ssr-dom-shim": { 861 | "version": "1.2.0", 862 | "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", 863 | "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==" 864 | }, 865 | "@lit/reactive-element": { 866 | "version": "1.6.3", 867 | "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", 868 | "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", 869 | "requires": { 870 | "@lit-labs/ssr-dom-shim": "^1.0.0" 871 | } 872 | }, 873 | "@nodelib/fs.scandir": { 874 | "version": "2.1.5", 875 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 876 | "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 877 | "dev": true, 878 | "requires": { 879 | "@nodelib/fs.stat": "2.0.5", 880 | "run-parallel": "^1.1.9" 881 | } 882 | }, 883 | "@nodelib/fs.stat": { 884 | "version": "2.0.5", 885 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 886 | "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 887 | "dev": true 888 | }, 889 | "@nodelib/fs.walk": { 890 | "version": "1.2.8", 891 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 892 | "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 893 | "dev": true, 894 | "requires": { 895 | "@nodelib/fs.scandir": "2.1.5", 896 | "fastq": "^1.6.0" 897 | } 898 | }, 899 | "@types/trusted-types": { 900 | "version": "2.0.7", 901 | "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 902 | "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" 903 | }, 904 | "@web/config-loader": { 905 | "version": "0.1.3", 906 | "resolved": "https://registry.npmjs.org/@web/config-loader/-/config-loader-0.1.3.tgz", 907 | "integrity": "sha512-XVKH79pk4d3EHRhofete8eAnqto1e8mCRAqPV00KLNFzCWSe8sWmLnqKCqkPNARC6nksMaGrATnA5sPDRllMpQ==", 908 | "dev": true, 909 | "requires": { 910 | "semver": "^7.3.4" 911 | } 912 | }, 913 | "anymatch": { 914 | "version": "3.1.3", 915 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 916 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 917 | "dev": true, 918 | "requires": { 919 | "normalize-path": "^3.0.0", 920 | "picomatch": "^2.0.4" 921 | } 922 | }, 923 | "array-back": { 924 | "version": "6.2.2", 925 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", 926 | "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", 927 | "dev": true 928 | }, 929 | "array-union": { 930 | "version": "2.1.0", 931 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", 932 | "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", 933 | "dev": true 934 | }, 935 | "binary-extensions": { 936 | "version": "2.2.0", 937 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 938 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 939 | "dev": true 940 | }, 941 | "braces": { 942 | "version": "3.0.2", 943 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 944 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 945 | "dev": true, 946 | "requires": { 947 | "fill-range": "^7.0.1" 948 | } 949 | }, 950 | "carbon-components": { 951 | "version": "10.58.3", 952 | "resolved": "https://registry.npmjs.org/carbon-components/-/carbon-components-10.58.3.tgz", 953 | "integrity": "sha512-RjTnrWCGStsIZ7nErw97AZI9sQWxQ8oIgo3QMdV0FWFcpTOECA4I9Dy4WPpRRdSMBcQpLetTxqjDGourM4u8Tw==", 954 | "requires": { 955 | "@carbon/telemetry": "0.1.0", 956 | "flatpickr": "4.6.1", 957 | "lodash.debounce": "^4.0.8", 958 | "warning": "^3.0.0" 959 | }, 960 | "dependencies": { 961 | "flatpickr": { 962 | "version": "4.6.1", 963 | "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.1.tgz", 964 | "integrity": "sha512-3ULSxbXmcMIRzer/2jLNweoqHpwDvsjEawO2FUd9UFR8uPwLM+LruZcPDpuZStcEgbQKhuFOfXo4nYdGladSNw==" 965 | } 966 | } 967 | }, 968 | "chart.js": { 969 | "version": "4.2.1", 970 | "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.2.1.tgz", 971 | "integrity": "sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==", 972 | "requires": { 973 | "@kurkle/color": "^0.3.0" 974 | } 975 | }, 976 | "chokidar": { 977 | "version": "3.5.2", 978 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", 979 | "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", 980 | "dev": true, 981 | "requires": { 982 | "anymatch": "~3.1.2", 983 | "braces": "~3.0.2", 984 | "fsevents": "~2.3.2", 985 | "glob-parent": "~5.1.2", 986 | "is-binary-path": "~2.1.0", 987 | "is-glob": "~4.0.1", 988 | "normalize-path": "~3.0.0", 989 | "readdirp": "~3.6.0" 990 | } 991 | }, 992 | "command-line-args": { 993 | "version": "5.1.2", 994 | "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.1.2.tgz", 995 | "integrity": "sha512-fytTsbndLbl+pPWtS0CxLV3BEWw9wJayB8NnU2cbQqVPsNdYezQeT+uIQv009m+GShnMNyuoBrRo8DTmuTfSCA==", 996 | "dev": true, 997 | "requires": { 998 | "array-back": "^6.1.2", 999 | "find-replace": "^3.0.0", 1000 | "lodash.camelcase": "^4.3.0", 1001 | "typical": "^4.0.0" 1002 | } 1003 | }, 1004 | "comment-parser": { 1005 | "version": "1.2.4", 1006 | "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.2.4.tgz", 1007 | "integrity": "sha512-pm0b+qv+CkWNriSTMsfnjChF9kH0kxz55y44Wo5le9qLxMj5xDQAaEd9ZN1ovSuk9CsrncWaFwgpOMg7ClJwkw==", 1008 | "dev": true 1009 | }, 1010 | "custom-elements-manifest": { 1011 | "version": "1.0.0", 1012 | "resolved": "https://registry.npmjs.org/custom-elements-manifest/-/custom-elements-manifest-1.0.0.tgz", 1013 | "integrity": "sha512-j59k0ExGCKA8T6Mzaq+7axc+KVHwpEphEERU7VZ99260npu/p/9kd+Db+I3cGKxHkM5y6q5gnlXn00mzRQkX2A==", 1014 | "dev": true 1015 | }, 1016 | "debounce": { 1017 | "version": "1.2.1", 1018 | "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", 1019 | "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", 1020 | "dev": true 1021 | }, 1022 | "dir-glob": { 1023 | "version": "3.0.1", 1024 | "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", 1025 | "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", 1026 | "dev": true, 1027 | "requires": { 1028 | "path-type": "^4.0.0" 1029 | } 1030 | }, 1031 | "es-module-lexer": { 1032 | "version": "0.9.3", 1033 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", 1034 | "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", 1035 | "dev": true 1036 | }, 1037 | "fast-glob": { 1038 | "version": "3.2.12", 1039 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", 1040 | "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", 1041 | "dev": true, 1042 | "requires": { 1043 | "@nodelib/fs.stat": "^2.0.2", 1044 | "@nodelib/fs.walk": "^1.2.3", 1045 | "glob-parent": "^5.1.2", 1046 | "merge2": "^1.3.0", 1047 | "micromatch": "^4.0.4" 1048 | } 1049 | }, 1050 | "fastq": { 1051 | "version": "1.15.0", 1052 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", 1053 | "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", 1054 | "dev": true, 1055 | "requires": { 1056 | "reusify": "^1.0.4" 1057 | } 1058 | }, 1059 | "fill-range": { 1060 | "version": "7.0.1", 1061 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1062 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1063 | "dev": true, 1064 | "requires": { 1065 | "to-regex-range": "^5.0.1" 1066 | } 1067 | }, 1068 | "find-replace": { 1069 | "version": "3.0.0", 1070 | "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", 1071 | "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", 1072 | "dev": true, 1073 | "requires": { 1074 | "array-back": "^3.0.1" 1075 | }, 1076 | "dependencies": { 1077 | "array-back": { 1078 | "version": "3.1.0", 1079 | "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", 1080 | "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", 1081 | "dev": true 1082 | } 1083 | } 1084 | }, 1085 | "flatpickr": { 1086 | "version": "4.6.13", 1087 | "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.13.tgz", 1088 | "integrity": "sha512-97PMG/aywoYpB4IvbvUJi0RQi8vearvU0oov1WW3k0WZPBMrTQVqekSX5CjSG/M4Q3i6A/0FKXC7RyAoAUUSPw==" 1089 | }, 1090 | "fsevents": { 1091 | "version": "2.3.2", 1092 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 1093 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 1094 | "dev": true, 1095 | "optional": true 1096 | }, 1097 | "glob-parent": { 1098 | "version": "5.1.2", 1099 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1100 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1101 | "dev": true, 1102 | "requires": { 1103 | "is-glob": "^4.0.1" 1104 | } 1105 | }, 1106 | "globby": { 1107 | "version": "11.0.4", 1108 | "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", 1109 | "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", 1110 | "dev": true, 1111 | "requires": { 1112 | "array-union": "^2.1.0", 1113 | "dir-glob": "^3.0.1", 1114 | "fast-glob": "^3.1.1", 1115 | "ignore": "^5.1.4", 1116 | "merge2": "^1.3.0", 1117 | "slash": "^3.0.0" 1118 | } 1119 | }, 1120 | "ignore": { 1121 | "version": "5.2.4", 1122 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", 1123 | "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", 1124 | "dev": true 1125 | }, 1126 | "is-binary-path": { 1127 | "version": "2.1.0", 1128 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1129 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1130 | "dev": true, 1131 | "requires": { 1132 | "binary-extensions": "^2.0.0" 1133 | } 1134 | }, 1135 | "is-extglob": { 1136 | "version": "2.1.1", 1137 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1138 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 1139 | "dev": true 1140 | }, 1141 | "is-glob": { 1142 | "version": "4.0.3", 1143 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1144 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1145 | "dev": true, 1146 | "requires": { 1147 | "is-extglob": "^2.1.1" 1148 | } 1149 | }, 1150 | "is-number": { 1151 | "version": "7.0.0", 1152 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1153 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 1154 | "dev": true 1155 | }, 1156 | "js-tokens": { 1157 | "version": "4.0.0", 1158 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1159 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" 1160 | }, 1161 | "lit": { 1162 | "version": "2.8.0", 1163 | "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", 1164 | "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", 1165 | "requires": { 1166 | "@lit/reactive-element": "^1.6.0", 1167 | "lit-element": "^3.3.0", 1168 | "lit-html": "^2.8.0" 1169 | } 1170 | }, 1171 | "lit-element": { 1172 | "version": "3.3.3", 1173 | "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", 1174 | "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", 1175 | "requires": { 1176 | "@lit-labs/ssr-dom-shim": "^1.1.0", 1177 | "@lit/reactive-element": "^1.3.0", 1178 | "lit-html": "^2.8.0" 1179 | } 1180 | }, 1181 | "lit-html": { 1182 | "version": "2.8.0", 1183 | "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", 1184 | "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", 1185 | "requires": { 1186 | "@types/trusted-types": "^2.0.2" 1187 | } 1188 | }, 1189 | "lodash-es": { 1190 | "version": "4.17.21", 1191 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 1192 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 1193 | }, 1194 | "lodash.camelcase": { 1195 | "version": "4.3.0", 1196 | "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", 1197 | "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", 1198 | "dev": true 1199 | }, 1200 | "lodash.debounce": { 1201 | "version": "4.0.8", 1202 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 1203 | "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" 1204 | }, 1205 | "loose-envify": { 1206 | "version": "1.4.0", 1207 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1208 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1209 | "requires": { 1210 | "js-tokens": "^3.0.0 || ^4.0.0" 1211 | } 1212 | }, 1213 | "lru-cache": { 1214 | "version": "6.0.0", 1215 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1216 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1217 | "dev": true, 1218 | "requires": { 1219 | "yallist": "^4.0.0" 1220 | } 1221 | }, 1222 | "merge2": { 1223 | "version": "1.4.1", 1224 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 1225 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 1226 | "dev": true 1227 | }, 1228 | "micromatch": { 1229 | "version": "4.0.5", 1230 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", 1231 | "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", 1232 | "dev": true, 1233 | "requires": { 1234 | "braces": "^3.0.2", 1235 | "picomatch": "^2.3.1" 1236 | } 1237 | }, 1238 | "ms": { 1239 | "version": "2.1.3", 1240 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1241 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1242 | }, 1243 | "normalize-path": { 1244 | "version": "3.0.0", 1245 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1246 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 1247 | "dev": true 1248 | }, 1249 | "path-type": { 1250 | "version": "4.0.0", 1251 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", 1252 | "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", 1253 | "dev": true 1254 | }, 1255 | "phoenix-custom-event-hook": { 1256 | "version": "0.0.6", 1257 | "resolved": "https://registry.npmjs.org/phoenix-custom-event-hook/-/phoenix-custom-event-hook-0.0.6.tgz", 1258 | "integrity": "sha512-6rPab4rL3UwhV1En4ACvwbyG7Y3o0/I2yJDpI6BgHdt8ie/LaMG+OL3MjYt67kGybWYGC/EhyXdUsZGng3dqtA==", 1259 | "requires": { 1260 | "ms": "^2.0.0" 1261 | } 1262 | }, 1263 | "picomatch": { 1264 | "version": "2.3.1", 1265 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 1266 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 1267 | "dev": true 1268 | }, 1269 | "queue-microtask": { 1270 | "version": "1.2.3", 1271 | "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 1272 | "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 1273 | "dev": true 1274 | }, 1275 | "readdirp": { 1276 | "version": "3.6.0", 1277 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1278 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1279 | "dev": true, 1280 | "requires": { 1281 | "picomatch": "^2.2.1" 1282 | } 1283 | }, 1284 | "regenerator-runtime": { 1285 | "version": "0.13.11", 1286 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", 1287 | "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" 1288 | }, 1289 | "reusify": { 1290 | "version": "1.0.4", 1291 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", 1292 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", 1293 | "dev": true 1294 | }, 1295 | "run-parallel": { 1296 | "version": "1.2.0", 1297 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 1298 | "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 1299 | "dev": true, 1300 | "requires": { 1301 | "queue-microtask": "^1.2.2" 1302 | } 1303 | }, 1304 | "semver": { 1305 | "version": "7.3.8", 1306 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", 1307 | "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", 1308 | "dev": true, 1309 | "requires": { 1310 | "lru-cache": "^6.0.0" 1311 | } 1312 | }, 1313 | "slash": { 1314 | "version": "3.0.0", 1315 | "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", 1316 | "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", 1317 | "dev": true 1318 | }, 1319 | "to-regex-range": { 1320 | "version": "5.0.1", 1321 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1322 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1323 | "dev": true, 1324 | "requires": { 1325 | "is-number": "^7.0.0" 1326 | } 1327 | }, 1328 | "typescript": { 1329 | "version": "4.3.5", 1330 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 1331 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 1332 | "dev": true 1333 | }, 1334 | "typical": { 1335 | "version": "4.0.0", 1336 | "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", 1337 | "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", 1338 | "dev": true 1339 | }, 1340 | "warning": { 1341 | "version": "3.0.0", 1342 | "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", 1343 | "integrity": "sha512-jMBt6pUrKn5I+OGgtQ4YZLdhIeJmObddh6CsibPxyQ5yPZm1XExSyzC1LCNX7BzhxWgiHmizBWJTHJIjMjTQYQ==", 1344 | "requires": { 1345 | "loose-envify": "^1.0.0" 1346 | } 1347 | }, 1348 | "yallist": { 1349 | "version": "4.0.0", 1350 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1351 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", 1352 | "dev": true 1353 | } 1354 | } 1355 | } 1356 | -------------------------------------------------------------------------------- /testbed/assets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "analyze": "cem analyze --dependencies --litelement" 4 | }, 5 | "dependencies": { 6 | "@carbon/web-components": "^1.24.0", 7 | "chart.js": "^4.2.1", 8 | "lit": "^2.6.1", 9 | "phoenix-custom-event-hook": "^0.0.6" 10 | }, 11 | "devDependencies": { 12 | "@custom-elements-manifest/analyzer": "^0.6.8" 13 | } 14 | } -------------------------------------------------------------------------------- /testbed/assets/tailwind.config.js: -------------------------------------------------------------------------------- 1 | // See the Tailwind configuration guide for advanced usage 2 | // https://tailwindcss.com/docs/configuration 3 | 4 | const plugin = require("tailwindcss/plugin") 5 | const fs = require("fs") 6 | const path = require("path") 7 | 8 | module.exports = { 9 | content: [ 10 | "./js/**/*.js", 11 | "../lib/*_web.ex", 12 | "../lib/*_web/**/*.*ex" 13 | ], 14 | theme: { 15 | extend: { 16 | colors: { 17 | brand: "#FD4F00", 18 | } 19 | }, 20 | }, 21 | plugins: [ 22 | require("@tailwindcss/forms"), 23 | // Allows prefixing tailwind classes with LiveView classes to add rules 24 | // only when LiveView classes are applied, for example: 25 | // 26 | //
27 | // 28 | plugin(({addVariant}) => addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])), 29 | plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), 30 | plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), 31 | plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), 32 | 33 | // Embeds Hero Icons (https://heroicons.com) into your app.css bundle 34 | // See your `CoreComponents.icon/1` for more information. 35 | // 36 | plugin(function({matchComponents, theme}) { 37 | let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized") 38 | let values = {} 39 | let icons = [ 40 | ["", "/24/outline"], 41 | ["-solid", "/24/solid"], 42 | ["-mini", "/20/solid"] 43 | ] 44 | icons.forEach(([suffix, dir]) => { 45 | fs.readdirSync(path.join(iconsDir, dir)).map(file => { 46 | let name = path.basename(file, ".svg") + suffix 47 | values[name] = {name, fullPath: path.join(iconsDir, dir, file)} 48 | }) 49 | }) 50 | matchComponents({ 51 | "hero": ({name, fullPath}) => { 52 | let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") 53 | return { 54 | [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, 55 | "-webkit-mask": `var(--hero-${name})`, 56 | "mask": `var(--hero-${name})`, 57 | "background-color": "currentColor", 58 | "vertical-align": "middle", 59 | "display": "inline-block", 60 | "width": theme("spacing.5"), 61 | "height": theme("spacing.5") 62 | } 63 | } 64 | }, {values}) 65 | }) 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /testbed/assets/vendor/topbar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license MIT 3 | * topbar 2.0.0, 2023-02-04 4 | * https://buunguyen.github.io/topbar 5 | * Copyright (c) 2021 Buu Nguyen 6 | */ 7 | (function (window, document) { 8 | "use strict"; 9 | 10 | // https://gist.github.com/paulirish/1579671 11 | (function () { 12 | var lastTime = 0; 13 | var vendors = ["ms", "moz", "webkit", "o"]; 14 | for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 15 | window.requestAnimationFrame = 16 | window[vendors[x] + "RequestAnimationFrame"]; 17 | window.cancelAnimationFrame = 18 | window[vendors[x] + "CancelAnimationFrame"] || 19 | window[vendors[x] + "CancelRequestAnimationFrame"]; 20 | } 21 | if (!window.requestAnimationFrame) 22 | window.requestAnimationFrame = function (callback, element) { 23 | var currTime = new Date().getTime(); 24 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 25 | var id = window.setTimeout(function () { 26 | callback(currTime + timeToCall); 27 | }, timeToCall); 28 | lastTime = currTime + timeToCall; 29 | return id; 30 | }; 31 | if (!window.cancelAnimationFrame) 32 | window.cancelAnimationFrame = function (id) { 33 | clearTimeout(id); 34 | }; 35 | })(); 36 | 37 | var canvas, 38 | currentProgress, 39 | showing, 40 | progressTimerId = null, 41 | fadeTimerId = null, 42 | delayTimerId = null, 43 | addEvent = function (elem, type, handler) { 44 | if (elem.addEventListener) elem.addEventListener(type, handler, false); 45 | else if (elem.attachEvent) elem.attachEvent("on" + type, handler); 46 | else elem["on" + type] = handler; 47 | }, 48 | options = { 49 | autoRun: true, 50 | barThickness: 3, 51 | barColors: { 52 | 0: "rgba(26, 188, 156, .9)", 53 | ".25": "rgba(52, 152, 219, .9)", 54 | ".50": "rgba(241, 196, 15, .9)", 55 | ".75": "rgba(230, 126, 34, .9)", 56 | "1.0": "rgba(211, 84, 0, .9)", 57 | }, 58 | shadowBlur: 10, 59 | shadowColor: "rgba(0, 0, 0, .6)", 60 | className: null, 61 | }, 62 | repaint = function () { 63 | canvas.width = window.innerWidth; 64 | canvas.height = options.barThickness * 5; // need space for shadow 65 | 66 | var ctx = canvas.getContext("2d"); 67 | ctx.shadowBlur = options.shadowBlur; 68 | ctx.shadowColor = options.shadowColor; 69 | 70 | var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); 71 | for (var stop in options.barColors) 72 | lineGradient.addColorStop(stop, options.barColors[stop]); 73 | ctx.lineWidth = options.barThickness; 74 | ctx.beginPath(); 75 | ctx.moveTo(0, options.barThickness / 2); 76 | ctx.lineTo( 77 | Math.ceil(currentProgress * canvas.width), 78 | options.barThickness / 2 79 | ); 80 | ctx.strokeStyle = lineGradient; 81 | ctx.stroke(); 82 | }, 83 | createCanvas = function () { 84 | canvas = document.createElement("canvas"); 85 | var style = canvas.style; 86 | style.position = "fixed"; 87 | style.top = style.left = style.right = style.margin = style.padding = 0; 88 | style.zIndex = 100001; 89 | style.display = "none"; 90 | if (options.className) canvas.classList.add(options.className); 91 | document.body.appendChild(canvas); 92 | addEvent(window, "resize", repaint); 93 | }, 94 | topbar = { 95 | config: function (opts) { 96 | for (var key in opts) 97 | if (options.hasOwnProperty(key)) options[key] = opts[key]; 98 | }, 99 | show: function (delay) { 100 | if (showing) return; 101 | if (delay) { 102 | if (delayTimerId) return; 103 | delayTimerId = setTimeout(() => topbar.show(), delay); 104 | } else { 105 | showing = true; 106 | if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); 107 | if (!canvas) createCanvas(); 108 | canvas.style.opacity = 1; 109 | canvas.style.display = "block"; 110 | topbar.progress(0); 111 | if (options.autoRun) { 112 | (function loop() { 113 | progressTimerId = window.requestAnimationFrame(loop); 114 | topbar.progress( 115 | "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) 116 | ); 117 | })(); 118 | } 119 | } 120 | }, 121 | progress: function (to) { 122 | if (typeof to === "undefined") return currentProgress; 123 | if (typeof to === "string") { 124 | to = 125 | (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 126 | ? currentProgress 127 | : 0) + parseFloat(to); 128 | } 129 | currentProgress = to > 1 ? 1 : to; 130 | repaint(); 131 | return currentProgress; 132 | }, 133 | hide: function () { 134 | clearTimeout(delayTimerId); 135 | delayTimerId = null; 136 | if (!showing) return; 137 | showing = false; 138 | if (progressTimerId != null) { 139 | window.cancelAnimationFrame(progressTimerId); 140 | progressTimerId = null; 141 | } 142 | (function loop() { 143 | if (topbar.progress("+.1") >= 1) { 144 | canvas.style.opacity -= 0.05; 145 | if (canvas.style.opacity <= 0.05) { 146 | canvas.style.display = "none"; 147 | fadeTimerId = null; 148 | return; 149 | } 150 | } 151 | fadeTimerId = window.requestAnimationFrame(loop); 152 | })(); 153 | }, 154 | }; 155 | 156 | if (typeof module === "object" && typeof module.exports === "object") { 157 | module.exports = topbar; 158 | } else if (typeof define === "function" && define.amd) { 159 | define(function () { 160 | return topbar; 161 | }); 162 | } else { 163 | this.topbar = topbar; 164 | } 165 | }.call(this, window, document)); 166 | -------------------------------------------------------------------------------- /testbed/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | # Configures the endpoint 11 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 12 | url: [host: "localhost"], 13 | render_errors: [ 14 | formats: [html: LiveElementsTestbedWeb.ErrorHTML, json: LiveElementsTestbedWeb.ErrorJSON], 15 | layout: false 16 | ], 17 | pubsub_server: LiveElementsTestbed.PubSub, 18 | live_view: [signing_salt: "WKPKidjT"] 19 | 20 | # Configures the mailer 21 | # 22 | # By default it uses the "Local" adapter which stores the emails 23 | # locally. You can see the emails in your browser, at "/dev/mailbox". 24 | # 25 | # For production it's recommended to configure a different adapter 26 | # at the `config/runtime.exs`. 27 | config :live_elements_testbed, LiveElementsTestbed.Mailer, adapter: Swoosh.Adapters.Local 28 | 29 | # Configure esbuild (the version is required) 30 | config :esbuild, 31 | version: "0.14.41", 32 | default: [ 33 | args: 34 | ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), 35 | cd: Path.expand("../assets", __DIR__), 36 | env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} 37 | ] 38 | 39 | # Configure tailwind (the version is required) 40 | config :tailwind, 41 | version: "3.2.4", 42 | default: [ 43 | args: ~w( 44 | --config=tailwind.config.js 45 | --input=css/app.css 46 | --output=../priv/static/assets/app.css 47 | ), 48 | cd: Path.expand("../assets", __DIR__) 49 | ] 50 | 51 | # Configures Elixir's Logger 52 | config :logger, :console, 53 | format: "$time $metadata[$level] $message\n", 54 | metadata: [:request_id] 55 | 56 | # Use Jason for JSON parsing in Phoenix 57 | config :phoenix, :json_library, Jason 58 | 59 | config :live_elements, custom_elements_manifest: Path.expand("../assets/custom-elements.json", __DIR__) 60 | 61 | # Import environment specific config. This must remain at the bottom 62 | # of this file so it overrides the configuration defined above. 63 | import_config "#{config_env()}.exs" 64 | -------------------------------------------------------------------------------- /testbed/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with esbuild to bundle .js and .css sources. 9 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 10 | # Binding to loopback ipv4 address prevents access from other machines. 11 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 12 | http: [ip: {127, 0, 0, 1}, port: 4000], 13 | check_origin: false, 14 | code_reloader: true, 15 | debug_errors: true, 16 | secret_key_base: "PwY8sS2Q8nzj19nnVnxYP13Cyo5ysMzNgZqkQo3a3gi7S68NOisup8w2uJhFXos+", 17 | watchers: [ 18 | esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}, 19 | tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} 20 | ] 21 | 22 | # ## SSL Support 23 | # 24 | # In order to use HTTPS in development, a self-signed 25 | # certificate can be generated by running the following 26 | # Mix task: 27 | # 28 | # mix phx.gen.cert 29 | # 30 | # Run `mix help phx.gen.cert` for more information. 31 | # 32 | # The `http:` config above can be replaced with: 33 | # 34 | # https: [ 35 | # port: 4001, 36 | # cipher_suite: :strong, 37 | # keyfile: "priv/cert/selfsigned_key.pem", 38 | # certfile: "priv/cert/selfsigned.pem" 39 | # ], 40 | # 41 | # If desired, both `http:` and `https:` keys can be 42 | # configured to run both http and https servers on 43 | # different ports. 44 | 45 | # Watch static and templates for browser reloading. 46 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 47 | live_reload: [ 48 | patterns: [ 49 | ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$", 50 | ~r"priv/gettext/.*(po)$", 51 | ~r"lib/live_elements_testbed_web/(controllers|live|components)/.*(ex|heex)$" 52 | ] 53 | ] 54 | 55 | # Enable dev routes for dashboard and mailbox 56 | config :live_elements_testbed, dev_routes: true 57 | 58 | # Do not include metadata nor timestamps in development logs 59 | config :logger, :console, format: "[$level] $message\n" 60 | 61 | # Set a higher stacktrace during development. Avoid configuring such 62 | # in production as building large stacktraces may be expensive. 63 | config :phoenix, :stacktrace_depth, 20 64 | 65 | # Initialize plugs at runtime for faster development compilation 66 | config :phoenix, :plug_init_mode, :runtime 67 | 68 | # Disable swoosh api client as it is only required for production adapters. 69 | config :swoosh, :api_client, false 70 | -------------------------------------------------------------------------------- /testbed/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" 13 | 14 | # Configures Swoosh API Client 15 | config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: LiveElementsTestbed.Finch 16 | 17 | # Do not print debug messages in production 18 | config :logger, level: :info 19 | 20 | # Runtime production configuration, including reading 21 | # of environment variables, is done on config/runtime.exs. 22 | -------------------------------------------------------------------------------- /testbed/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/live_elements_testbed start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | # The secret key base is used to sign/encrypt cookies and other secrets. 25 | # A default value is used in config/dev.exs and config/test.exs but you 26 | # want to use a different value for prod and you most likely don't want 27 | # to check this value into version control, so we use an environment 28 | # variable instead. 29 | secret_key_base = 30 | System.get_env("SECRET_KEY_BASE") || 31 | raise """ 32 | environment variable SECRET_KEY_BASE is missing. 33 | You can generate one by calling: mix phx.gen.secret 34 | """ 35 | 36 | host = System.get_env("PHX_HOST") || "example.com" 37 | port = String.to_integer(System.get_env("PORT") || "4000") 38 | 39 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 40 | url: [host: host, port: 443, scheme: "https"], 41 | http: [ 42 | # Enable IPv6 and bind on all interfaces. 43 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 44 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 45 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 46 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 47 | port: port 48 | ], 49 | secret_key_base: secret_key_base 50 | 51 | # ## SSL Support 52 | # 53 | # To get SSL working, you will need to add the `https` key 54 | # to your endpoint configuration: 55 | # 56 | # config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 57 | # https: [ 58 | # ..., 59 | # port: 443, 60 | # cipher_suite: :strong, 61 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 62 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 63 | # ] 64 | # 65 | # The `cipher_suite` is set to `:strong` to support only the 66 | # latest and more secure SSL ciphers. This means old browsers 67 | # and clients may not be supported. You can set it to 68 | # `:compatible` for wider support. 69 | # 70 | # `:keyfile` and `:certfile` expect an absolute path to the key 71 | # and cert in disk or a relative path inside priv, for example 72 | # "priv/ssl/server.key". For all supported SSL configuration 73 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 74 | # 75 | # We also recommend setting `force_ssl` in your endpoint, ensuring 76 | # no data is ever sent via http, always redirecting to https: 77 | # 78 | # config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 79 | # force_ssl: [hsts: true] 80 | # 81 | # Check `Plug.SSL` for all available options in `force_ssl`. 82 | 83 | # ## Configuring the mailer 84 | # 85 | # In production you need to configure the mailer to use a different adapter. 86 | # Also, you may need to configure the Swoosh API client of your choice if you 87 | # are not using SMTP. Here is an example of the configuration: 88 | # 89 | # config :live_elements_testbed, LiveElementsTestbed.Mailer, 90 | # adapter: Swoosh.Adapters.Mailgun, 91 | # api_key: System.get_env("MAILGUN_API_KEY"), 92 | # domain: System.get_env("MAILGUN_DOMAIN") 93 | # 94 | # For this example you need include a HTTP client required by Swoosh API client. 95 | # Swoosh supports Hackney and Finch out of the box: 96 | # 97 | # config :swoosh, :api_client, Swoosh.ApiClient.Hackney 98 | # 99 | # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. 100 | end 101 | -------------------------------------------------------------------------------- /testbed/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :live_elements_testbed, LiveElementsTestbedWeb.Endpoint, 6 | http: [ip: {127, 0, 0, 1}, port: 4002], 7 | secret_key_base: "Y/uVoLUDplrYfzv2jcPanL06RYD/MeulRJ08cq2UZapD/vuRkiAR7wWIzt8a/kFw", 8 | server: true 9 | 10 | # In test we don't send emails. 11 | config :live_elements_testbed, LiveElementsTestbed.Mailer, 12 | adapter: Swoosh.Adapters.Test 13 | 14 | # Disable swoosh api client as it is only required for production adapters. 15 | config :swoosh, :api_client, false 16 | 17 | # Print only warnings and errors during test 18 | config :logger, level: :warning 19 | 20 | # Initialize plugs at runtime for faster test compilation 21 | config :phoenix, :plug_init_mode, :runtime 22 | 23 | config :wallaby, 24 | otp_app: :live_elements_testbed, 25 | base_url: "http://localhost:4002" 26 | # chromedriver: [ 27 | # headless: false 28 | # ] 29 | -------------------------------------------------------------------------------- /testbed/lib/live_elements_testbed.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTestbed do 2 | @moduledoc """ 3 | LiveElementsTestbed keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /testbed/lib/live_elements_testbed/application.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTestbed.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Start the Telemetry supervisor 12 | LiveElementsTestbedWeb.Telemetry, 13 | # Start the PubSub system 14 | {Phoenix.PubSub, name: LiveElementsTestbed.PubSub}, 15 | # Start Finch 16 | {Finch, name: LiveElementsTestbed.Finch}, 17 | # Start the Endpoint (http/https) 18 | LiveElementsTestbedWeb.Endpoint 19 | # Start a worker by calling: LiveElementsTestbed.Worker.start_link(arg) 20 | # {LiveElementsTestbed.Worker, arg} 21 | ] 22 | 23 | # See https://hexdocs.pm/elixir/Supervisor.html 24 | # for other strategies and supported options 25 | opts = [strategy: :one_for_one, name: LiveElementsTestbed.Supervisor] 26 | Supervisor.start_link(children, opts) 27 | end 28 | 29 | # Tell Phoenix to update the endpoint configuration 30 | # whenever the application is updated. 31 | @impl true 32 | def config_change(changed, _new, removed) do 33 | LiveElementsTestbedWeb.Endpoint.config_change(changed, removed) 34 | :ok 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /testbed/lib/live_elements_testbed/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTestbed.Mailer do 2 | use Swoosh.Mailer, otp_app: :live_elements_testbed 3 | end 4 | -------------------------------------------------------------------------------- /testbed/lib/live_elements_testbed_web.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTestbedWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, components, channels, and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use LiveElementsTestbedWeb, :controller 9 | use LiveElementsTestbedWeb, :html 10 | 11 | The definitions below will be executed for every controller, 12 | component, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define additional modules and import 17 | those modules here. 18 | """ 19 | 20 | def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) 21 | 22 | def router do 23 | quote do 24 | use Phoenix.Router, helpers: false 25 | 26 | # Import common connection and controller functions to use in pipelines 27 | import Plug.Conn 28 | import Phoenix.Controller 29 | import Phoenix.LiveView.Router 30 | end 31 | end 32 | 33 | def channel do 34 | quote do 35 | use Phoenix.Channel 36 | end 37 | end 38 | 39 | def controller do 40 | quote do 41 | use Phoenix.Controller, 42 | formats: [:html, :json], 43 | layouts: [html: LiveElementsTestbedWeb.Layouts] 44 | 45 | import Plug.Conn 46 | import LiveElementsTestbedWeb.Gettext 47 | 48 | unquote(verified_routes()) 49 | end 50 | end 51 | 52 | def live_view do 53 | quote do 54 | use Phoenix.LiveView, 55 | layout: {LiveElementsTestbedWeb.Layouts, :app} 56 | 57 | unquote(html_helpers()) 58 | end 59 | end 60 | 61 | def live_component do 62 | quote do 63 | use Phoenix.LiveComponent 64 | 65 | unquote(html_helpers()) 66 | end 67 | end 68 | 69 | def html do 70 | quote do 71 | use Phoenix.Component 72 | 73 | # Import convenience functions from controllers 74 | import Phoenix.Controller, 75 | only: [get_csrf_token: 0, view_module: 1, view_template: 1] 76 | 77 | # Include general helpers for rendering HTML 78 | unquote(html_helpers()) 79 | end 80 | end 81 | 82 | defp html_helpers do 83 | quote do 84 | # HTML escaping functionality 85 | import Phoenix.HTML 86 | # Core UI components and translation 87 | import LiveElementsTestbedWeb.CoreComponents 88 | import LiveElementsTestbedWeb.Gettext 89 | 90 | # Shortcut for generating JS commands 91 | alias Phoenix.LiveView.JS 92 | 93 | # Routes generation with the ~p sigil 94 | unquote(verified_routes()) 95 | end 96 | end 97 | 98 | def verified_routes do 99 | quote do 100 | use Phoenix.VerifiedRoutes, 101 | endpoint: LiveElementsTestbedWeb.Endpoint, 102 | router: LiveElementsTestbedWeb.Router, 103 | statics: LiveElementsTestbedWeb.static_paths() 104 | end 105 | end 106 | 107 | @doc """ 108 | When used, dispatch to the appropriate controller/view/etc. 109 | """ 110 | defmacro __using__(which) when is_atom(which) do 111 | apply(__MODULE__, which, []) 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /testbed/lib/live_elements_testbed_web/components/core_components.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveElementsTestbedWeb.CoreComponents do 2 | @moduledoc """ 3 | Provides core UI components. 4 | 5 | The components in this module use Tailwind CSS, a utility-first CSS framework. 6 | See the [Tailwind CSS documentation](https://tailwindcss.com) to learn how to 7 | customize the generated components in this module. 8 | 9 | Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. 10 | """ 11 | use Phoenix.Component 12 | 13 | alias Phoenix.LiveView.JS 14 | import LiveElementsTestbedWeb.Gettext 15 | 16 | # custom_element :bx_paginate, events: [] 17 | @doc """ 18 | Renders a modal. 19 | 20 | ## Examples 21 | 22 | <.modal id="confirm-modal"> 23 | Are you sure? 24 | <:confirm>OK 25 | <:cancel>Cancel 26 | 27 | 28 | JS commands may be passed to the `:on_cancel` and `on_confirm` attributes 29 | for the caller to react to each button press, for example: 30 | 31 | <.modal id="confirm" on_confirm={JS.push("delete")} on_cancel={JS.navigate(~p"/posts")}> 32 | Are you sure you? 33 | <:confirm>OK 34 | <:cancel>Cancel 35 | 36 | """ 37 | attr :id, :string, required: true 38 | attr :show, :boolean, default: false 39 | attr :on_cancel, JS, default: %JS{} 40 | attr :on_confirm, JS, default: %JS{} 41 | 42 | slot :inner_block, required: true 43 | slot :title 44 | slot :subtitle 45 | slot :confirm 46 | slot :cancel 47 | 48 | def modal(assigns) do 49 | ~H""" 50 |