diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml
new file mode 100644
index 0000000..660edaf
--- /dev/null
+++ b/.github/workflows/build-and-deploy.yml
@@ -0,0 +1,34 @@
+name: Build and Deploy
+on:
+ push:
+ tags:
+ - v*
+permissions:
+ contents: write
+jobs:
+ build-and-deploy:
+ runs-on: ubuntu-latest
+ environment: deploy
+ steps:
+ - name: Checkout ๐๏ธ
+ uses: actions/checkout@v3
+
+ - name: Use Node.js ๐
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - name: Install and Build ๐ง
+ run: |
+ npm ci
+ npm run build-ci
+
+ - name: Deploy ๐
+ uses: JamesIves/github-pages-deploy-action@v4
+ with:
+ folder: .
+
+ - name: Publish to NPM ๐
+ uses: JS-DevTools/npm-publish@v2
+ with:
+ token: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..b20f8ad
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,24 @@
+name: Test
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout ๐๐๐ฅค
+ uses: actions/checkout@v3
+ with:
+ persist-credentials: false
+
+ - name: Use Node.js ๐
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+
+ - name: Test ๐งช
+ run: |
+ npm ci
+ npm run check-ci
diff --git a/.gitignore b/.gitignore
index c15b84f..bcf9cc5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,10 @@
+# -- clip-for-deploy-start --
+
+/docs
+/dist
+
+# -- clip-for-deploy-end --
+
*.pyc
.DS_Store
node_modules
diff --git a/build/prep-for-deploy.js b/build/prep-for-deploy.js
new file mode 100644
index 0000000..8bc69c9
--- /dev/null
+++ b/build/prep-for-deploy.js
@@ -0,0 +1,9 @@
+import fs from 'fs';
+import path from 'path';
+import * as url from 'url';
+const dirname = url.fileURLToPath(new URL('.', import.meta.url));
+
+const ignoreFilename = path.join(dirname, '..', '..', '.gitignore');
+const ignore = fs.readFileSync(ignoreFilename, {encoding: 'utf8'});
+const newIgnore = ignore.replace(/# -- clip-for-deploy-start --[\s\S]*?# -- clip-for-deploy-end --/, '');
+fs.writeFileSync(ignoreFilename, newIgnore);
diff --git a/dist/0.x/muigui.js b/dist/0.x/muigui.js
deleted file mode 100644
index 3e3bb6f..0000000
--- a/dist/0.x/muigui.js
+++ /dev/null
@@ -1,3831 +0,0 @@
-/* muigui@0.0.10, license MIT */
-(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.GUI = factory());
-})(this, (function () { 'use strict';
-
- var css = {
- default: `
-.muigui {
- --bg-color: #ddd;
- --color: #222;
- --contrast-color: #eee;
- --value-color: #145 ;
- --value-bg-color: #eeee;
- --disabled-color: #999;
- --menu-bg-color: #f8f8f8;
- --menu-sep-color: #bbb;
- --hover-bg-color: #999;
- --focus-color: #68C;
- --range-color: #888888;
- --invalid-color: #FF0000;
- --selected-color: rgb(255, 255, 255, 0.9);
-
- --button-bg-color: var(--value-bg-color);
-
- --range-left-color: var(--value-color);
- --range-right-color: var(--value-bg-color);
- --range-right-hover-color: var(--hover-bg-color);
-
- color: var(--color);
- background-color: var(--bg-color);
-}
-
-@media (prefers-color-scheme: dark) {
- .muigui {
- --bg-color: #222222;
- --color: #dddddd;
- --contrast-color: #000;
- --value-color: #43e5f7;
- --value-bg-color: #444444;
- --disabled-color: #666666;
- --menu-bg-color: #080808;
- --menu-sep-color: #444444;
- --hover-bg-color: #666666;
- --focus-color: #88AAFF;
- --range-color: #888888;
- --invalid-color: #FF6666;
- --selected-color: rgba(255, 255, 255, 0.3);
-
- --button-bg-color: var(--value-bg-color);
-
- --range-left-color: var(--value-color);
- --range-right-color: var(--value-bg-color);
- --range-right-hover-color: var(--hover-bg-color);
-
- color: var(--color);
- background-color: var(--bg-color);
- }
-}
-
-.muigui {
- --width: 250px;
- --label-width: 45%;
- --number-width: 40%;
-
-
- --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
- --font-size: 11px;
- --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace;
- --font-size-mono: 11px;
-
- --line-height: 1.7em;
- --border-radius: 0px;
-
- width: var(--width);
- font-family: var(--font-family);
- font-size: var(--font-size);
- box-sizing: border-box;
- line-height: 100%;
-}
-.muigui * {
- box-sizing: inherit;
-}
-
-.muigui-no-scroll {
- touch-action: none;
-}
-.muigui-no-h-scroll {
- touch-action: pan-y;
-}
-.muigui-no-v-scroll {
- touch-action: pan-x;
-}
-
-.muigui-invalid-value {
- background-color: red !important;
- color: white !important;
-}
-
-.muigui-grid {
- display: grid;
-}
-.muigui-rows {
- display: flex;
- flex-direction: column;
-
- min-height: 20px;
- border: 2px solid red;
-}
-.muigui-columns {
- display: flex;
- flex-direction: row;
-
- height: 20px;
- border: 2px solid green;
-}
-.muigui-rows>*,
-.muigui-columns>* {
- flex: 1 1 auto;
- align-items: stretch;
- min-height: 0;
- min-width: 0;
-}
-
-.muigui-row {
- border: 2px solid yellow;
- min-height: 10px
-}
-.muigui-column {
- border: 2px solid lightgreen;
-}
-
-/* -------- */
-
-.muigui-show { /* */ }
-.muigui-hide {
- display: none !important;
-}
-.muigui-disabled {
- pointer-events: none;
- --color: var(--disabled-color) !important;
- --value-color: var(--disabled-color) !important;
- --range-left-color: var(--disabled-color) !important;
-}
-
-.muigui canvas,
-.muigui svg {
- display: block;
- border-radius: var(--border-radius);
-}
-.muigui canvas {
- background-color: var(--value-bg-color);
-}
-
-.muigui-controller {
- min-width: 0;
- min-height: var(--line-height);
-}
-.muigui-root,
-.muigui-menu {
- display: flex;
- flex-direction: column;
- position: relative;
- user-select: none;
- height: fit-content;
- margin: 0;
- padding-bottom: 0.1em;
- border-radius: var(--border-radius);
-}
-.muigui-menu {
- border-bottom: 1px solid var(--menu-sep-color);
-}
-
-.muigui-root>button:nth-child(1),
-.muigui-menu>button:nth-child(1) {
- border-top: 1px solid var(--menu-sep-color);
- border-bottom: 1px solid var(--menu-sep-color);
- position: relative;
- text-align: left;
- color: var(--color);
- background-color: var(--menu-bg-color);
- min-height: var(--line-height);
- padding-top: 0.2em;
- padding-bottom: 0.2em;
- cursor: pointer;
- border-radius: var(--border-radius);
-}
-.muigui-root>div:nth-child(2),
-.muigui-menu>div:nth-child(2) {
- flex: 1 1 auto;
-}
-
-.muigui-controller {
- margin-left: 0.2em;
- margin-right: 0.2em;
-}
-.muigui-root.muigui-controller,
-.muigui-menu.muigui-controller {
- margin-left: 0;
- margin-right: 0;
-}
-.muigui-controller>*:nth-child(1) {
- flex: 1 0 var(--label-width);
- min-width: 0;
- white-space: pre;
-}
-.muigui-controller>label:nth-child(1) {
- place-content: center start;
- display: inline-grid;
- overflow: hidden;
-}
-.muigui-controller>*:nth-child(2) {
- flex: 1 1 75%;
- min-width: 0;
-}
-
-/* -----------------------------------------
- a label controller is [[label][value]]
-*/
-
-.muigui-label-controller {
- display: flex;
- margin: 0.4em 0 0.4em 0;
- word-wrap: initial;
- align-items: stretch;
-}
-
-.muigui-value {
- display: flex;
- align-items: stretch;
-}
-.muigui-value>* {
- flex: 1 1 auto;
- min-width: 0;
-}
-.muigui-value>*:nth-child(1) {
- flex: 1 1 calc(100% - var(--number-width));
-}
-.muigui-value>*:nth-child(2) {
- flex: 1 1 var(--number-width);
- margin-left: 0.2em;
-}
-
-/* fix! */
-.muigui-open>button>label::before,
-.muigui-closed>button>label::before {
- width: 1.25em;
- height: var(--line-height);
- display: inline-grid;
- place-content: center start;
- pointer-events: none;
-}
-.muigui-open>button>label::before {
- content: "โง"; /*"โผ";*/
-}
-.muigui-closed>button>label::before {
- content: "โจ"; /*"โถ";*/
-}
-.muigui-open>*:nth-child(2) {
- transition: max-height 0.2s ease-out,
- opacity 0.5s ease-out;
- max-height: 100vh;
- overflow: auto;
- opacity: 1;
-}
-
-.muigui-closed>*:nth-child(2) {
- transition: max-height 0.2s ease-out,
- opacity 1s;
- max-height: 0;
- opacity: 0;
- overflow: hidden;
-}
-
-/* ---- popdown ---- */
-
-.muigui-pop-down-top {
- display: flex;
-}
-/* fix? */
-.muigui-value>*:nth-child(1).muigui-pop-down-top {
- flex: 0;
-}
-.muigui-pop-down-bottom {
-
-}
-
-.muigui-pop-down-values {
- min-width: 0;
- display: flex;
-}
-.muigui-pop-down-values>* {
- flex: 1 1 auto;
- min-width: 0;
-}
-
-.muigui-value.muigui-pop-down-controller {
- flex-direction: column;
-}
-
-.muigui-pop-down-top input[type=checkbox] {
- -webkit-appearance: none;
- appearance: none;
- width: auto;
- color: var(--value-color);
- background-color: var(--value-bg-color);
- cursor: pointer;
-
- display: grid;
- place-content: center;
- margin: 0;
- font: inherit;
- color: currentColor;
- width: 1.7em;
- height: 1.7em;
- transform: translateY(-0.075em);
-}
-
-.muigui-pop-down-top input[type=checkbox]::before {
- content: "+";
- display: grid;
- place-content: center;
- border-radius: calc(var(--border-radius) + 2px);
- border-left: 1px solid rgba(255,255,255,0.3);
- border-top: 1px solid rgba(255,255,255,0.3);
- border-bottom: 1px solid rgba(0,0,0,0.2);
- border-right: 1px solid rgba(0,0,0,0.2);
- background-color: var(--range-color);
- color: var(--value-bg-color);
- width: calc(var(--line-height) - 4px);
- height: calc(var(--line-height) - 4px);
-}
-
-.muigui-pop-down-top input[type=checkbox]:checked::before {
- content: "๏ผธ";
-}
-
-
-/* ---- select ---- */
-
-.muigui select,
-.muigui option,
-.muigui input,
-.muigui button {
- color: var(--value-color);
- background-color: var(--value-bg-color);
- font-family: var(--font-family);
- font-size: var(--font-size);
- border: none;
- margin: 0;
- border-radius: var(--border-radius);
-}
-.muigui select {
- appearance: none;
- margin: 0;
- margin-left: 0; /*?*/
- overflow: hidden; /* Safari */
-}
-
-.muigui select:focus,
-.muigui input:focus,
-.muigui button:focus {
- outline: 1px solid var(--focus-color);
-}
-
-.muigui select:hover,
-.muigui option:hover,
-.muigui input:hover,
-.muigui button:hover {
- background-color: var(--hover-bg-color);
-}
-
-/* ------ [ label ] ------ */
-
-.muigui-label {
- border-top: 1px solid var(--menu-sep-color);
- border-bottom: 1px solid var(--menu-sep-color);
- padding-top: 0.4em;
- padding-bottom: 0.3em;
- place-content: center start;
- background-color: var(--menu-bg-color);
- white-space: pre;
- border-radius: var(--border-radius);
-}
-
-/* ------ [ divider] ------ */
-
-.muigui-divider {
- min-height: 6px;
- border-top: 2px solid var(--menu-sep-color);
- margin-top: 6px;
-}
-
-/* ------ [ button ] ------ */
-
-.muigui-button {
- display: grid;
-
-}
-.muigui-button button {
- border: none;
- color: var(--value-color);
- background-color: var(--button-bg-color);
- cursor: pointer;
- place-content: center center;
-}
-
-/* ------ [ color ] ------ */
-
-.muigui-color>div {
- overflow: hidden;
- position: relative;
- margin-left: 0;
- margin-right: 0; /* why? */
- max-width: var(--line-height);
- border-radius: var(--border-radius);
-}
-
-.muigui-color>div:focus-within {
- outline: 1px solid var(--focus-color);
-}
-
-.muigui-color input[type=color] {
- border: none;
- padding: 0;
- background: inherit;
- cursor: pointer;
- position: absolute;
- width: 200%;
- left: -10px;
- top: -10px;
- height: 200%;
-}
-.muigui-disabled canvas,
-.muigui-disabled svg,
-.muigui-disabled img,
-.muigui-disabled .muigui-color input[type=color] {
- opacity: 0.2;
-}
-
-/* ------ [ checkbox ] ------ */
-
-.muigui-checkbox>label:nth-child(2) {
- display: grid;
- place-content: center start;
- margin: 0;
-}
-
-.muigui-checkbox input[type=checkbox] {
- -webkit-appearance: none;
- appearance: none;
- width: auto;
- color: var(--value-color);
- background-color: var(--value-bg-color);
- cursor: pointer;
-
- display: grid;
- place-content: center;
- margin: 0;
- font: inherit;
- color: currentColor;
- width: 1.7em;
- height: 1.7em;
- transform: translateY(-0.075em);
-}
-
-.muigui-checkbox input[type=checkbox]::before {
- content: "";
- color: var(--value-color);
- display: grid;
- place-content: center;
-}
-
-.muigui-checkbox input[type=checkbox]:checked::before {
- content: "โ";
-}
-
-.muigui input[type=number]::-webkit-inner-spin-button,
-.muigui input[type=number]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- appearance: none;
- margin: 0;
-}
-.muigui input[type=number] {
- -moz-appearance: textfield;
-}
-
-/* ------ [ radio grid ] ------ */
-
-.muigui-radio-grid>div {
- display: grid;
- gap: 2px;
-}
-
-.muigui-radio-grid input {
- appearance: none;
- display: none;
-}
-
-.muigui-radio-grid button {
- color: var(--color);
- width: 100%;
- text-align: left;
-}
-
-.muigui-radio-grid input:checked + button {
- color: var(--value-color);
- background-color: var(--selected-color);
-}
-
-/* ------ [ color-chooser ] ------ */
-
-.muigui-color-chooser-cursor {
- stroke-width: 1px;
- stroke: white;
- fill: none;
-}
-.muigui-color-chooser-circle {
- stroke-width: 1px;
- stroke: white;
- fill: none;
-}
-
-
-/* ------ [ vec2 ] ------ */
-
-.muigui-vec2 svg {
- background-color: var(--value-bg-color);
-}
-
-.muigui-vec2-axis {
- stroke: 1px;
- stroke: var(--focus-color);
-}
-
-.muigui-vec2-line {
- stroke-width: 1px;
- stroke: var(--value-color);
- fill: var(--value-color);
-}
-
-/* ------ [ direction ] ------ */
-
-.muigui-direction svg {
- background-color: rgba(0,0,0,0.2);
-}
-
-.muigui-direction:focus-within svg {
- outline: none;
-}
-.muigui-direction-range {
- fill: var(--value-bg-color);
-}
-.muigui-direction svg:focus {
- outline: none;
-}
-.muigui-direction svg:focus .muigui-direction-range {
- stroke-width: 0.5px;
- stroke: var(--focus-color);
-}
-
-.muigui-direction-arrow {
- fill: var(--value-color);
-}
-
-/* ------ [ slider ] ------ */
-
-.muigui-slider>div {
- display: flex;
- align-items: stretch;
- height: var(--line-height);
-}
-.muigui-slider svg {
- flex: 1 1 auto;
-}
-.muigui-slider .muigui-slider-up #muigui-orientation {
- transform: scale(1, -1) translateY(-100%);
-}
-
-.muigui-slider .muigui-slider-up #muigui-number-orientation {
- transform: scale(1,-1);
-}
-
-.muigui-ticks {
- stroke: var(--range-color);
-}
-.muigui-thicks {
- stroke: var(--color);
- stroke-width: 2px;
-}
-.muigui-svg-text {
- fill: var(--color);
- font-size: 7px;
-}
-.muigui-mark {
- fill: var(--value-color);
-}
-
-/* ------ [ range ] ------ */
-
-
-.muigui-range input[type=range] {
- -webkit-appearance: none;
- appearance: none;
- background-color: transparent;
-}
-
-.muigui-range input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- border-radius: calc(var(--border-radius) + 2px);
- border-left: 1px solid rgba(255,255,255,0.3);
- border-top: 1px solid rgba(255,255,255,0.3);
- border-bottom: 1px solid rgba(0,0,0,0.2);
- border-right: 1px solid rgba(0,0,0,0.2);
- background-color: var(--range-color);
- margin-top: calc((var(--line-height) - 2px) / -2);
- width: calc(var(--line-height) - 2px);
- height: calc(var(--line-height) - 2px);
-}
-
-.muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: none;
- appearance: none;
- border: 1px solid var(--menu-sep-color);
- height: 2px;
-}
-
-
-/* dat.gui style - doesn't work on Safari iOS */
-
-/*
-.muigui-range input[type=range] {
- cursor: ew-resize;
- overflow: hidden;
-}
-
-.muigui-range input[type=range] {
- -webkit-appearance: none;
- appearance: none;
- background-color: var(--range-right-color);
- margin: 0;
-}
-.muigui-range input[type=range]:hover {
- background-color: var(--range-right-hover-color);
-}
-
-.muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: none;
- appearance: none;
- height: max-content;
- color: var(--range-left-color);
- margin-top: -1px;
-}
-
-.muigui-range input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 0px;
- height: max-content;
- box-shadow: -1000px 0 0 1000px var(--range-left-color);
-}
-*/
-
-/* FF */
-/*
-.muigui-range input[type=range]::-moz-slider-progress {
- background-color: var(--range-left-color);
-}
-.muigui-range input[type=range]::-moz-slider-thumb {
- height: max-content;
- width: 0;
- border: none;
- box-shadow: -1000px 0 0 1000px var(--range-left-color);
- box-sizing: border-box;
-}
-*/
-
-.muigui-checkered-background {
- background-color: #404040;
- background-image:
- linear-gradient(45deg, #808080 25%, transparent 25%),
- linear-gradient(-45deg, #808080 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, #808080 75%),
- linear-gradient(-45deg, transparent 75%, #808080 75%);
- background-size: 16px 16px;
- background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
-}
-
-/* ---------------------------------------------------------- */
-
-/* needs to be at bottom to take precedence */
-.muigui-auto-place {
- max-height: 100%;
- position: fixed;
- top: 0;
- right: 15px;
- z-index: 100001;
-}
-
-`,
- themes: {
- default: '',
- float: `
- :root {
- color-scheme: light dark,
- }
-
- .muigui {
- --width: 400px;
- --bg-color: initial;
- --label-width: 25%;
- --number-width: 20%;
- }
-
- input,
- .muigui-label-controller>label {
- text-shadow:
- -1px -1px 0 var(--contrast-color),
- 1px -1px 0 var(--contrast-color),
- -1px 1px 0 var(--contrast-color),
- 1px 1px 0 var(--contrast-color);
- }
-
- .muigui-controller > label:nth-child(1) {
- place-content: center end;
- margin-right: 1em;
- }
-
- .muigui-value > :nth-child(2) {
- margin-left: 1em;
- }
-
- .muigui-root>*:nth-child(1) {
- display: none;
- }
-
- .muigui-range input[type=range]::-webkit-slider-thumb {
- border-radius: 1em;
- }
-
- .muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: initial;
- appearance: none;
- border: 1px solid rgba(0, 0, 0, 0.25);
- height: 2px;
- }
-
- .muigui-colors {
- --value-color: var(--color );
- --value-bg-color: rgba(0, 0, 0, 0.1);
- --disabled-color: #cccccc;
- --menu-bg-color: rgba(0, 0, 0, 0.1);
- --menu-sep-color: #bbbbbb;
- --hover-bg-color: rgba(0, 0, 0, 0);
- --invalid-color: #FF0000;
- --selected-color: rgba(0, 0, 0, 0.3);
- --range-color: rgba(0, 0, 0, 0.125);
- }
-`,
- },
- };
-
- function setElemProps(elem, attrs, children) {
- for (const [key, value] of Object.entries(attrs)) {
- if (typeof value === 'function' && key.startsWith('on')) {
- const eventName = key.substring(2).toLowerCase();
- elem.addEventListener(eventName, value, {passive: false});
- } else if (typeof value === 'object') {
- for (const [k, v] of Object.entries(value)) {
- elem[key][k] = v;
- }
- } else if (elem[key] === undefined) {
- elem.setAttribute(key, value);
- } else {
- elem[key] = value;
- }
- }
- for (const child of children) {
- elem.appendChild(child);
- }
- return elem;
- }
-
- function createElem(tag, attrs = {}, children = []) {
- const elem = document.createElement(tag);
- setElemProps(elem, attrs, children);
- return elem;
- }
-
- function addElem(tag, parent, attrs = {}, children = []) {
- const elem = createElem(tag, attrs, children);
- parent.appendChild(elem);
- return elem;
- }
-
- let nextId = 0;
- function getNewId() {
- return `muigui-id-${nextId++}`;
- }
-
- function removeArrayElem(array, value) {
- const ndx = array.indexOf(value);
- if (ndx) {
- array.splice(ndx, 1);
- }
- return array;
- }
-
- /**
- * Converts an camelCase or snake_case id to "camel case" or "snake case"
- * @param {string} id
- */
- const underscoreRE = /_/g;
- const upperLowerRE = /([A-Z])([a-z])/g;
- function idToLabel(id) {
- return id.replace(underscoreRE, ' ')
- .replace(upperLowerRE, (m, m1, m2) => `${m1.toLowerCase()} ${m2}`);
- }
-
- function clamp$1(v, min, max) {
- return Math.max(min, Math.min(max, v));
- }
-
- const isTypedArray = typeof SharedArrayBuffer !== 'undefined'
- ? function isArrayBufferOrSharedArrayBuffer(a) {
- return a && a.buffer && (a.buffer instanceof ArrayBuffer || a.buffer instanceof SharedArrayBuffer);
- }
- : function isArrayBuffer(a) {
- return a && a.buffer && a.buffer instanceof ArrayBuffer;
- };
-
- const isArrayOrTypedArray = v => Array.isArray(v) || isTypedArray(v);
-
- // Yea, I know this should be `Math.round(v / step) * step
- // but try step = 0.1, newV = 19.95
- //
- // I get
- // Math.round(19.95 / 0.1) * 0.1
- // 19.900000000000002
- // vs
- // Math.round(19.95 / 0.1) / (1 / 0.1)
- // 19.9
- //
- const stepify = (v, from, step) => Math.round(from(v) / step) / (1 / step);
-
- const euclideanModulo$1 = (v, n) => ((v % n) + n) % n;
- const lerp$1 = (a, b, t) => a + (b - a) * t;
- function copyExistingProperties(dst, src) {
- for (const key in src) {
- if (key in dst) {
- dst[key] = src[key];
- }
- }
- return dst;
- }
-
- const mapRange = (v, inMin, inMax, outMin, outMax) => (v - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
-
- const makeRangeConverters = ({from, to}) => {
- return {
- to: v => mapRange(v, ...from, ...to),
- from: v => [true, mapRange(v, ...to, ...from)],
- };
- };
-
- const makeRangeOptions = ({from, to, step}) => {
- return {
- min: to[0],
- max: to[1],
- ...(step && {step}),
- converters: makeRangeConverters({from, to}),
- };
- };
-
- // TODO: remove an use one in conversions. Move makeRangeConverters there?
- const identity$1 = {
- to: v => v,
- from: v => [true, v],
- };
- function makeMinMaxPair(gui, properties, minPropName, maxPropName, options) {
- const { converters: { from } = identity$1 } = options;
- const { min, max } = options;
- const guiMinRange = options.minRange || 0;
- const valueMinRange = from(guiMinRange)[1];
- const minGui = gui
- .add(properties, minPropName, {
- ...options,
- min,
- max: max - guiMinRange,
- })
- .onChange(v => {
- maxGui.setValue(Math.min(max, Math.max(v + valueMinRange, properties[maxPropName])));
- });
- const maxGui = gui
- .add(properties, maxPropName, {
- ...options,
- min: min + guiMinRange,
- max,
- })
- .onChange(v => {
- minGui.setValue(Math.max(min, Math.min(v - valueMinRange, properties[minPropName])));
- });
- return [ minGui, maxGui ];
- }
-
- class View {
- domElement;
- #childDestElem;
- #views = [];
- constructor(elem) {
- this.domElement = elem;
- this.#childDestElem = elem;
- }
- addElem(elem) {
- this.#childDestElem.appendChild(elem);
- return elem;
- }
- removeElem(elem) {
- this.#childDestElem.removeChild(elem);
- return elem;
- }
- pushSubElem(elem) {
- this.#childDestElem.appendChild(elem);
- this.#childDestElem = elem;
- }
- popSubElem() {
- this.#childDestElem = this.#childDestElem.parentElement;
- }
- add(view) {
- this.#views.push(view);
- this.addElem(view.domElement);
- return view;
- }
- remove(view) {
- this.removeElem(view.domElement);
- removeArrayElem(this.#views, view);
- return view;
- }
- pushSubView(view) {
- this.pushSubElem(view.domElement);
- }
- popSubView() {
- this.popSubElem();
- }
- setOptions(options) {
- for (const view of this.#views) {
- view.setOptions(options);
- }
- }
- updateDisplayIfNeeded(newV, ignoreCache) {
- for (const view of this.#views) {
- view.updateDisplayIfNeeded(newV, ignoreCache);
- }
- return this;
- }
- $(selector) {
- return this.domElement.querySelector(selector);
- }
- }
-
- class Controller extends View {
- #changeFns;
- #finishChangeFns;
- #parent;
-
- constructor(className) {
- super(createElem('div', {className: 'muigui-controller'}));
- this.#changeFns = [];
- this.#finishChangeFns = [];
- // we need the specialization to come last so it takes precedence.
- if (className) {
- this.domElement.classList.add(className);
- }
- }
- get parent() {
- return this.#parent;
- }
- setParent(parent) {
- this.#parent = parent;
- this.enable(!this.disabled());
- }
- show(show = true) {
- this.domElement.classList.toggle('muigui-hide', !show);
- this.domElement.classList.toggle('muigui-show', show);
- return this;
- }
- hide() {
- return this.show(false);
- }
- disabled() {
- return !!this.domElement.closest('.muigui-disabled');
- }
-
- enable(enable = true) {
- this.domElement.classList.toggle('muigui-disabled', !enable);
-
- // If disabled we need to set the attribute 'disabled=true' to all
- // input/select/button/textarea's below
- //
- // If enabled we need to set the attribute 'disabled=false' to all below
- // until we hit a disabled controller.
- //
- // ATM the problem is we can find the input/select/button/textarea elements
- // but we can't easily find which controller they belong do.
- // But we don't need to? We can just check up if it or parent has
- // '.muigui-disabled'
- ['input', 'button', 'select', 'textarea'].forEach(tag => {
- this.domElement.querySelectorAll(tag).forEach(elem => {
- const disabled = !!elem.closest('.muigui-disabled');
- elem.disabled = disabled;
- });
- });
-
- return this;
- }
- disable(disable = true) {
- return this.enable(!disable);
- }
- onChange(fn) {
- this.removeChange(fn);
- this.#changeFns.push(fn);
- return this;
- }
- removeChange(fn) {
- removeArrayElem(this.#changeFns, fn);
- return this;
- }
- onFinishChange(fn) {
- this.removeFinishChange(fn);
- this.#finishChangeFns.push(fn);
- return this;
- }
- removeFinishChange(fn) {
- removeArrayElem(this.#finishChangeFns, fn);
- return this;
- }
- #callListeners(fns, newV) {
- for (const fn of fns) {
- fn.call(this, newV);
- }
- }
- emitChange(value, object, property) {
- this.#callListeners(this.#changeFns, value);
- if (this.#parent) {
- if (object === undefined) {
- this.#parent.emitChange(value);
- } else {
- this.#parent.emitChange({
- object,
- property,
- value,
- controller: this,
- });
- }
- }
- }
- emitFinalChange(value, object, property) {
- this.#callListeners(this.#finishChangeFns, value);
- if (this.#parent) {
- if (object === undefined) {
- this.#parent.emitChange(value);
- } else {
- this.#parent.emitFinalChange({
- object,
- property,
- value,
- controller: this,
- });
- }
- }
- }
- updateDisplay() {
- // placeholder. override
- }
- getColors() {
- const toCamelCase = s => s.replace(/-([a-z])/g, (m, m1) => m1.toUpperCase());
- const keys = [
- 'color',
- 'bg-color',
- 'value-color',
- 'value-bg-color',
- 'hover-bg-color',
- 'menu-bg-color',
- 'menu-sep-color',
- 'disabled-color',
- ];
- const div = createElem('div');
- this.domElement.appendChild(div);
- const colors = Object.fromEntries(keys.map(key => {
- div.style.color = `var(--${key})`;
- const s = getComputedStyle(div);
- return [toCamelCase(key), s.color];
- }));
- div.remove();
- return colors;
- }
- }
-
- class Button extends Controller {
- #object;
- #property;
- #buttonElem;
- #options = {
- name: '',
- };
-
- constructor(object, property, options = {}) {
- super('muigui-button', '');
- this.#object = object;
- this.#property = property;
-
- this.#buttonElem = this.addElem(
- createElem('button', {
- type: 'button',
- onClick: () => {
- this.#object[this.#property](this);
- },
- }));
- this.setOptions({name: property, ...options});
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {name} = this.#options;
- this.#buttonElem.textContent = name;
- }
- }
-
- function arraysEqual(a, b) {
- if (a.length !== b.length) {
- return false;
- }
- for (let i = 0; i < a.length; ++i) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
- }
-
- function copyArrayElementsFromTo(src, dst) {
- dst.length = src.length;
- for (let i = 0; i < src.length; ++i) {
- dst[i] = src[i];
- }
- }
-
- class EditView extends View {
- #oldV;
- #updateCheck;
-
- #checkArrayNeedsUpdate(newV) {
- // It's an array, we need to compare all elements
- // Example, vec2, [r,g,b], ...
- const needUpdate = !arraysEqual(newV, this.#oldV);
- if (needUpdate) {
- copyArrayElementsFromTo(newV, this.#oldV);
- }
- return needUpdate;
- }
-
- #checkTypedArrayNeedsUpdate() {
- let once = true;
- return function checkTypedArrayNeedsUpdateImpl(newV) {
- // It's a typedarray, we need to compare all elements
- // Example: Float32Array([r, g, b])
- let needUpdate = once;
- once = false;
- if (!needUpdate) {
- needUpdate = !arraysEqual(newV, this.#oldV);
- }
- return needUpdate;
- };
- }
-
- #checkObjectNeedsUpdate(newV) {
- let needUpdate = false;
- for (const key in newV) {
- if (newV[key] !== this.#oldV[key]) {
- needUpdate = true;
- this.#oldV[key] = newV[key];
- }
- }
- return needUpdate;
- }
-
- #checkValueNeedsUpdate(newV) {
- const needUpdate = newV !== this.#oldV;
- this.#oldV = newV;
- return needUpdate;
- }
-
- #getUpdateCheckForType(newV) {
- if (Array.isArray(newV)) {
- this.#oldV = [];
- return this.#checkArrayNeedsUpdate.bind(this);
- } else if (isTypedArray(newV)) {
- this.#oldV = new newV.constructor(newV);
- return this.#checkTypedArrayNeedsUpdate(this);
- } else if (typeof newV === 'object') {
- this.#oldV = {};
- return this.#checkObjectNeedsUpdate.bind(this);
- } else {
- return this.#checkValueNeedsUpdate.bind(this);
- }
- }
-
- // The point of this is updating DOM elements
- // is slow but if we've called `listen` then
- // every frame we're going to try to update
- // things with the current value so if nothing
- // has changed then skip it.
- updateDisplayIfNeeded(newV, ignoreCache) {
- this.#updateCheck = this.#updateCheck || this.#getUpdateCheckForType(newV);
- // Note: We call #updateCheck first because it updates
- // the cache
- if (this.#updateCheck(newV) || ignoreCache) {
- this.updateDisplay(newV);
- }
- }
- setOptions(/*options*/) {
- // override this
- return this;
- }
- }
-
- class CheckboxView extends EditView {
- #checkboxElem;
- constructor(setter, id) {
- const checkboxElem = createElem('input', {
- type: 'checkbox',
- id,
- onInput: () => {
- setter.setValue(checkboxElem.checked);
- },
- onChange: () => {
- setter.setFinalValue(checkboxElem.checked);
- },
- });
- super(createElem('label', {}, [checkboxElem]));
- this.#checkboxElem = checkboxElem;
- }
- updateDisplay(v) {
- this.#checkboxElem.checked = v;
- }
- }
-
- const tasks = [];
- const tasksToRemove = new Set();
-
- let requestId;
- let processing;
-
- function removeTasks() {
- if (!tasksToRemove.size) {
- return;
- }
-
- if (processing) {
- queueProcessing();
- return;
- }
-
- tasksToRemove.forEach(task => {
- removeArrayElem(tasks, task);
- });
- tasksToRemove.clear();
- }
-
- function processTasks() {
- requestId = undefined;
- processing = true;
- for (const task of tasks) {
- if (!tasksToRemove.has(task)) {
- task();
- }
- }
- processing = false;
- removeTasks();
- queueProcessing();
- }
-
- function queueProcessing() {
- if (!requestId && tasks.length) {
- requestId = requestAnimationFrame(processTasks);
- }
- }
-
- function addTask(fn) {
- tasks.push(fn);
- queueProcessing();
- }
-
- function removeTask(fn) {
- tasksToRemove.set(fn);
-
- const ndx = tasks.indexOf(fn);
- if (ndx >= 0) {
- tasks.splice(ndx, 1);
- }
- }
-
- let id = 0;
-
- function makeId() {
- return `muigui-${++id}`;
- }
-
- class ValueView extends View {
- constructor(className = '') {
- super(createElem('div', {className: 'muigui-value'}));
- if (className) {
- this.domElement.classList.add(className);
- }
- }
- }
-
- class LabelController extends Controller {
- #id;
- #nameElem;
-
- constructor(className = '', name = '') {
- super('muigui-label-controller');
- this.#id = makeId();
- this.#nameElem = createElem('label', {for: this.#id});
- this.domElement.appendChild(this.#nameElem);
- this.pushSubView(new ValueView(className));
- this.name(name);
- }
- get id() {
- return this.#id;
- }
- name(name) {
- if (this.#nameElem.title === this.#nameElem.textContent) {
- this.#nameElem.title = name;
- }
- this.#nameElem.textContent = name;
- return this;
- }
- tooltip(tip) {
- this.#nameElem.title = tip;
- }
- }
-
- class ValueController extends LabelController {
- #object;
- #property;
- #initialValue;
- #listening;
- #views;
- #updateFn;
-
- constructor(object, property, className = '') {
- super(className, property);
- this.#object = object;
- this.#property = property;
- this.#initialValue = this.getValue();
- this.#listening = false;
- this.#views = [];
- }
- get initialValue() {
- return this.#initialValue;
- }
- get object() {
- return this.#object;
- }
- get property() {
- return this.#property;
- }
- add(view) {
- this.#views.push(view);
- super.add(view);
- this.updateDisplay();
- return view;
- }
- #setValueImpl(v, ignoreCache) {
- let isDifferent = false;
- if (typeof v === 'object') {
- const dst = this.#object[this.#property];
- // don't replace objects, just their values.
- if (Array.isArray(v) || isTypedArray(v)) {
- for (let i = 0; i < v.length; ++i) {
- isDifferent ||= dst[i] !== v[i];
- dst[i] = v[i];
- }
- } else {
- for (const key of Object.keys(v)) {
- isDifferent ||= dst[key] !== v[key];
- }
- Object.assign(dst, v);
- }
- } else {
- isDifferent = this.#object[this.#property] !== v;
- this.#object[this.#property] = v;
- }
- this.updateDisplay(ignoreCache);
- if (isDifferent) {
- this.emitChange(this.getValue(), this.#object, this.#property);
- }
- return isDifferent;
- }
- setValue(v) {
- this.#setValueImpl(v);
- }
- setFinalValue(v) {
- const isDifferent = this.#setValueImpl(v, true);
- if (isDifferent) {
- this.emitFinalChange(this.getValue(), this.#object, this.#property);
- }
- return this;
- }
- updateDisplay(ignoreCache) {
- const newV = this.getValue();
- for (const view of this.#views) {
- view.updateDisplayIfNeeded(newV, ignoreCache);
- }
- return this;
- }
- setOptions(options) {
- for (const view of this.#views) {
- view.setOptions(options);
- }
- this.updateDisplay();
- return this;
- }
- getValue() {
- return this.#object[this.#property];
- }
- value(v) {
- this.setValue(v);
- return this;
- }
- reset() {
- this.setValue(this.#initialValue);
- return this;
- }
- listen(listen = true) {
- if (!this.#updateFn) {
- this.#updateFn = this.updateDisplay.bind(this);
- }
- if (listen) {
- if (!this.#listening) {
- this.#listening = true;
- addTask(this.#updateFn);
- }
- } else {
- if (this.#listening) {
- this.#listening = false;
- removeTask(this.#updateFn);
- }
- }
- return this;
- }
- }
-
- class Checkbox extends ValueController {
- constructor(object, property) {
- super(object, property, 'muigui-checkbox');
- const id = this.id;
- this.add(new CheckboxView(this, id));
- this.updateDisplay();
- }
- }
-
- const identity = {
- to: v => v,
- from: v => [true, v],
- };
-
- // from: from string to value
- // to: from value to string
- const strToNumber = {
- to: v => v.toString(),
- from: v => {
- const newV = parseFloat(v);
- return [!Number.isNaN(newV), newV];
- },
- };
-
- const converters = {
- radToDeg: makeRangeConverters({to: [0, 180], from: [0, Math.PI]}),
- };
-
- function createWheelHelper() {
- let wheelAccum = 0;
- return function(e, step, wheelScale = 5) {
- wheelAccum -= e.deltaY * step / wheelScale;
- const wheelSteps = Math.floor(Math.abs(wheelAccum) / step) * Math.sign(wheelAccum);
- const delta = wheelSteps * step;
- wheelAccum -= delta;
- return delta;
- };
- }
-
- class NumberView extends EditView {
- #to;
- #from;
- #step;
- #skipUpdate;
- #options = {
- step: 0.01,
- converters: strToNumber,
- min: Number.NEGATIVE_INFINITY,
- max: Number.POSITIVE_INFINITY,
- };
-
- constructor(setter, options) {
- const setValue = setter.setValue.bind(setter);
- const setFinalValue = setter.setFinalValue.bind(setter);
- const wheelHelper = createWheelHelper();
- super(createElem('input', {
- type: 'number',
- onInput: () => this.#handleInput(setValue, true),
- onChange: () => this.#handleInput(setFinalValue, false),
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.setOptions(options);
- }
- #handleInput(setFn, skipUpdate) {
- const v = parseFloat(this.domElement.value);
- const [valid, newV] = this.#from(v);
- let inRange;
- if (valid && !Number.isNaN(v)) {
- const {min, max} = this.#options;
- inRange = newV >= min && newV <= max;
- this.#skipUpdate = skipUpdate;
- setFn(clamp$1(newV, min, max));
- }
- this.domElement.classList.toggle('muigui-invalid-value', !valid || !inRange);
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = stepify(v, this.#to, this.#step);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- step,
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- this.#step = step;
- return this;
- }
- }
-
- // Wanted to name this `Number` but it conflicts with
- // JavaScript `Number`. It most likely wouldn't be
- // an issue? But users might `import {Number} ...` and
- // things would break.
- class TextNumber extends ValueController {
- #textView;
- #step;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-checkbox');
- this.#textView = this.add(new NumberView(this, options));
- this.updateDisplay();
- }
- }
-
- class SelectView extends EditView {
- #values;
-
- constructor(setter, keyValues) {
- const values = [];
- super(createElem('select', {
- onChange: () => {
- setter.setFinalValue(this.#values[this.domElement.selectedIndex]);
- },
- }, keyValues.map(([key, value]) => {
- values.push(value);
- return createElem('option', {textContent: key});
- })));
- this.#values = values;
- }
- updateDisplay(v) {
- const ndx = this.#values.indexOf(v);
- this.domElement.selectedIndex = ndx;
- }
- }
-
- // 4 cases
- // (a) keyValues is array of arrays, each sub array is key value
- // (b) keyValues is array and value is number then keys = array contents, value = index
- // (c) keyValues is array and value is not number, key = array contents, value = array contents
- // (d) keyValues is object then key->value
- function convertToKeyValues(keyValues, valueIsNumber) {
- if (Array.isArray(keyValues)) {
- if (Array.isArray(keyValues[0])) {
- // (a) keyValues is array of arrays, each sub array is key value
- return keyValues;
- } else {
- if (valueIsNumber) {
- // (b) keyValues is array and value is number then keys = array contents, value = index
- return keyValues.map((v, ndx) => [v, ndx]);
- } else {
- // (c) keyValues is array and value is not number, key = array contents, value = array contents
- return keyValues.map(v => [v, v]);
- }
- }
- } else {
- // (d)
- return [...Object.entries(keyValues)];
- }
- }
-
- class Select extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-select');
- const valueIsNumber = typeof this.getValue() === 'number';
- const {keyValues: keyValuesInput} = options;
- const keyValues = convertToKeyValues(keyValuesInput, valueIsNumber);
- this.add(new SelectView(this, keyValues));
- this.updateDisplay();
- }
- }
-
- class RangeView extends EditView {
- #to;
- #from;
- #step;
- #skipUpdate;
- #options = {
- step: 0.01,
- min: 0,
- max: 1,
- converters: identity,
- };
-
- constructor(setter, options) {
- const wheelHelper = createWheelHelper();
- super(createElem('input', {
- type: 'range',
- onInput: () => {
- this.#skipUpdate = true;
- const {min, max, step} = this.#options;
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v, v => v, step), min, max);
- const [valid, validV] = this.#from(newV);
- if (valid) {
- setter.setValue(validV);
- }
- },
- onChange: () => {
- this.#skipUpdate = true;
- const {min, max, step} = this.#options;
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v, v => v, step), min, max);
- const [valid, validV] = this.#from(newV);
- if (valid) {
- setter.setFinalValue(validV);
- }
- },
- onWheel: e => {
- e.preventDefault();
- const [valid, v] = this.#from(parseFloat(this.domElement.value));
- if (!valid) {
- return;
- }
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const newV = clamp$1(stepify(v + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.setOptions(options);
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = stepify(v, this.#to, this.#step);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- step,
- min,
- max,
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- this.#step = step;
- this.domElement.step = step;
- this.domElement.min = min;
- this.domElement.max = max;
- return this;
- }
- }
-
- class Range extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-range');
- this.add(new RangeView(this, options));
- this.add(new NumberView(this, options));
- }
- }
-
- class TextView extends EditView {
- #to;
- #from;
- #skipUpdate;
- #options = {
- converters: identity,
- };
-
- constructor(setter, options) {
- const setValue = setter.setValue.bind(setter);
- const setFinalValue = setter.setFinalValue.bind(setter);
- super(createElem('input', {
- type: 'text',
- onInput: () => this.#handleInput(setValue, true),
- onChange: () => this.#handleInput(setFinalValue, false),
- }));
- this.setOptions(options);
- }
- #handleInput(setFn, skipUpdate) {
- const [valid, newV] = this.#from(this.domElement.value);
- if (valid) {
- this.#skipUpdate = skipUpdate;
- setFn(newV);
- }
- this.domElement.style.color = valid ? '' : 'var(--invalid-color)';
-
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = this.#to(v);
- this.domElement.style.color = '';
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- return this;
- }
- }
-
- class Text extends ValueController {
- constructor(object, property) {
- super(object, property, 'muigui-checkbox');
- this.add(new TextView(this));
- this.updateDisplay();
- }
- }
-
- // const isConversion = o => typeof o.to === 'function' && typeof o.from === 'function';
-
- /**
- * possible inputs
- * add(o, p, min: number, max: number)
- * add(o, p, min: number, max: number, step: number)
- * add(o, p, array: [value])
- * add(o, p, array: [[key, value]])
- *
- * @param {*} object
- * @param {string} property
- * @param {...any} args
- * @returns {Controller}
- */
- function createController(object, property, ...args) {
- const [arg1] = args;
- if (Array.isArray(arg1)) {
- return new Select(object, property, {keyValues: arg1});
- }
-
- const t = typeof object[property];
- switch (t) {
- case 'number':
- if (typeof args[0] === 'number' && typeof args[1] === 'number') {
- const min = args[0];
- const max = args[1];
- const step = args[2];
- return new Range(object, property, {min, max, ...(step && {step})});
- }
- return args.length === 0
- ? new TextNumber(object, property, ...args)
- : new Range(object, property, ...args);
- case 'boolean':
- return new Checkbox(object, property, ...args);
- case 'function':
- return new Button(object, property, ...args);
- case 'string':
- return new Text(object, property, ...args);
- case 'undefined':
- throw new Error(`no property named ${property}`);
- default:
- throw new Error(`unhandled type ${t} for property ${property}`);
- }
- }
-
- const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
- const lerp = (a, b, t) => a + (b - a) * t;
- const fract = v => v >= 0 ? v % 1 : 1 - (v % 1);
-
- const f0 = v => +v.toFixed(0); // converts to string (eg 1.2 => "1"), then converts back to number (eg, "1.200" => 1.2)
- const f3 = v => +v.toFixed(3); // converts to string (eg 1.2 => "1.200"), then converts back to number (eg, "1.200" => 1.2)
-
- const hexToUint32RGB = v => (parseInt(v.substring(1, 3), 16) << 16) |
- (parseInt(v.substring(3, 5), 16) << 8 ) |
- (parseInt(v.substring(5, 7), 16) );
- const uint32RGBToHex = v => `#${(Math.round(v)).toString(16).padStart(6, '0')}`;
- const hexToUint32RGBA = v => (parseInt(v.substring(1, 3), 16) * 2 ** 24) +
- (parseInt(v.substring(3, 5), 16) * 2 ** 16) +
- (parseInt(v.substring(5, 7), 16) * 2 ** 8) +
- (parseInt(v.substring(7, 9), 16) );
- const uint32RGBAToHex = v => `#${(Math.round(v)).toString(16).padStart(8, '0')}`;
-
- const hexToUint8RGB = v => [
- parseInt(v.substring(1, 3), 16),
- parseInt(v.substring(3, 5), 16),
- parseInt(v.substring(5, 7), 16),
- ];
- const uint8RGBToHex = v => `#${Array.from(v).map(v => v.toString(16).padStart(2, '0')).join('')}`;
-
- const hexToUint8RGBA = v => [
- parseInt(v.substring(1, 3), 16),
- parseInt(v.substring(3, 5), 16),
- parseInt(v.substring(5, 7), 16),
- parseInt(v.substring(7, 9), 16),
- ];
- const uint8RGBAToHex = v => `#${Array.from(v).map(v => v.toString(16).padStart(2, '0')).join('')}`;
-
- const hexToFloatRGB = v => hexToUint8RGB(v).map(v => f3(v / 255));
- const floatRGBToHex = v => uint8RGBToHex(Array.from(v).map(v => Math.round(clamp(v * 255, 0, 255))));
-
- const hexToFloatRGBA = v => hexToUint8RGBA(v).map(v => f3(v / 255));
- const floatRGBAToHex = v => uint8RGBAToHex(Array.from(v).map(v => Math.round(clamp(v * 255, 0, 255))));
-
- const scaleAndClamp = v => clamp(Math.round(v * 255), 0, 255).toString(16).padStart(2, '0');
-
- const hexToObjectRGB = v => ({
- r: parseInt(v.substring(1, 3), 16) / 255,
- g: parseInt(v.substring(3, 5), 16) / 255,
- b: parseInt(v.substring(5, 7), 16) / 255,
- });
- const objectRGBToHex = v => `#${scaleAndClamp(v.r)}${scaleAndClamp(v.g)}${scaleAndClamp(v.b)}`;
- const hexToObjectRGBA = v => ({
- r: parseInt(v.substring(1, 3), 16) / 255,
- g: parseInt(v.substring(3, 5), 16) / 255,
- b: parseInt(v.substring(5, 7), 16) / 255,
- a: parseInt(v.substring(7, 9), 16) / 255,
- });
- const objectRGBAToHex = v => `#${scaleAndClamp(v.r)}${scaleAndClamp(v.g)}${scaleAndClamp(v.b)}${scaleAndClamp(v.a)}`;
-
- const hexToCssRGB = v => `rgb(${hexToUint8RGB(v).join(', ')})`;
- const cssRGBRegex = /^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/;
- const cssRGBToHex = v => {
- const m = cssRGBRegex.exec(v);
- return uint8RGBToHex([m[1], m[2], m[3]].map(v => parseInt(v)));
- };
- const hexToCssRGBA = v => `rgba(${hexToUint8RGBA(v).map((v, i) => i === 3 ? v / 255 : v).join(', ')})`;
- const cssRGBARegex = /^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+\.\d+|\d+)\s*\)\s*$/;
- const cssRGBAToHex = v => {
- const m = cssRGBARegex.exec(v);
- return uint8RGBAToHex([m[1], m[2], m[3], m[4]].map((v, i) => i === 3 ? (parseFloat(v) * 255 | 0) : parseInt(v)));
- };
-
- const hexToCssHSL = v => {
- const hsl = rgbUint8ToHsl(hexToUint8RGB(v)).map(v => f0(v));
- return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
- };
- const hexToCssHSLA = v => {
- const hsla = rgbaUint8ToHsla(hexToUint8RGBA(v)).map((v, i) => i === 3 ? f3(v) : f0(v));
- return `hsl(${hsla[0]} ${hsla[1]}% ${hsla[2]}% / ${hsla[3]})`;
- };
- const cssHSLRegex = /^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\)\s*$/;
- const cssHSLARegex = /^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\/\s*(\d+\.\d+|\d+)\s*\)\s*$/;
-
- const hex3DigitTo6Digit = v => `${v[0]}${v[0]}${v[1]}${v[1]}${v[2]}${v[2]}`;
- const cssHSLToHex = v => {
- const m = cssHSLRegex.exec(v);
- const rgb = hslToRgbUint8([m[1], m[2], m[3]].map(v => parseFloat(v)));
- return uint8RGBToHex(rgb);
- };
- const cssHSLAToHex = v => {
- const m = cssHSLARegex.exec(v);
- const rgba = hslaToRgbaUint8([m[1], m[2], m[3], m[4]].map(v => parseFloat(v)));
- return uint8RGBAToHex(rgba);
- };
-
- const euclideanModulo = (v, n) => ((v % n) + n) % n;
-
- function hslToRgbUint8([h, s, l]) {
- h = euclideanModulo(h, 360);
- s = clamp(s / 100, 0, 1);
- l = clamp(l / 100, 0, 1);
-
- const a = s * Math.min(l, 1 - l);
-
- function f(n) {
- const k = (n + h / 30) % 12;
- return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
- }
-
- return [f(0), f(8), f(4)].map(v => Math.round(v * 255));
- }
-
- function hslaToRgbaUint8([h, s, l, a]) {
- const rgb = hslToRgbUint8([h, s, l]);
- return [...rgb, a * 255 | 0];
- }
-
- function rgbFloatToHsl01([r, g, b]) {
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const l = (min + max) * 0.5;
- const d = max - min;
- let h = 0;
- let s = 0;
-
- if (d !== 0) {
- s = (l === 0 || l === 1)
- ? 0
- : (max - l) / Math.min(l, 1 - l);
-
- switch (max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4;
- }
- }
-
- return [h / 6, s, l];
- }
-
- function rgbaFloatToHsla01([r, g, b, a]) {
- const hsl = rgbFloatToHsl01([r, g, b]);
- return [...hsl, a];
- }
-
- const rgbUint8ToHsl = (rgb) => {
- const [h, s, l] = rgbFloatToHsl01(rgb.map(v => v / 255));
- return [h * 360, s * 100, l * 100];
- };
-
- const rgbaUint8ToHsla = (rgba) => {
- const [h, s, l, a] = rgbaFloatToHsla01(rgba.map(v => v / 255));
- return [h * 360, s * 100, l * 100, a];
- };
-
- function hsv01ToRGBFloat([hue, sat, val]) {
- sat = clamp(sat, 0, 1);
- val = clamp(val, 0, 1);
- return [hue, hue + 2 / 3, hue + 1 / 3].map(
- v => lerp(1, clamp(Math.abs(fract(v) * 6 - 3.0) - 1, 0, 1), sat) * val
- );
- }
-
- function hsva01ToRGBAFloat([hue, sat, val, alpha]) {
- const rgb = hsv01ToRGBFloat([hue, sat, val]);
- return [...rgb, alpha];
- }
-
- const round3 = v => Math.round(v * 1000) / 1000;
-
- function rgbFloatToHSV01([r, g, b]) {
- const p = b > g
- ? [b, g, -1, 2 / 3]
- : [g, b, 0, -1 / 3];
- const q = p[0] > r
- ? [p[0], p[1], p[3], r]
- : [r, p[1], p[2], p[0]];
- const d = q[0] - Math.min(q[3], q[1]);
- return [
- Math.abs(q[2] + (q[3] - q[1]) / (6 * d + Number.EPSILON)),
- d / (q[0] + Number.EPSILON),
- q[0],
- ].map(round3);
- }
-
- function rgbaFloatToHSVA01([r, g, b, a]) {
- const hsv = rgbFloatToHSV01([r, g, b]);
- return [...hsv, a];
- }
-
- // window.hsv01ToRGBFloat = hsv01ToRGBFloat;
- // window.rgbFloatToHSV01 = rgbFloatToHSV01;
-
- // Yea, meh!
- const hasAlpha = format => format.endsWith('a') || format.startsWith('hex8');
-
- const cssStringFormats = [
- { re: /^#(?:[0-9a-f]){6}$/i, format: 'hex6' },
- { re: /^(?:[0-9a-f]){6}$/i, format: 'hex6-no-hash' },
- { re: /^#(?:[0-9a-f]){8}$/i, format: 'hex8' },
- { re: /^(?:[0-9a-f]){8}$/i, format: 'hex8-no-hash' },
- { re: /^#(?:[0-9a-f]){3}$/i, format: 'hex3' },
- { re: /^(?:[0-9a-f]){3}$/i, format: 'hex3-no-hash' },
- { re: cssRGBRegex, format: 'css-rgb' },
- { re: cssHSLRegex, format: 'css-hsl' },
- { re: cssRGBARegex, format: 'css-rgba' },
- { re: cssHSLARegex, format: 'css-hsla' },
- ];
-
- function guessStringColorFormat(v) {
- for (const formatInfo of cssStringFormats) {
- if (formatInfo.re.test(v)) {
- return formatInfo;
- }
- }
- return undefined;
- }
-
- function guessFormat(v) {
- switch (typeof v) {
- case 'number':
- console.warn('can not reliably guess format based on a number. You should pass in a format like {format: "uint32-rgb"} or {format: "uint32-rgb"}');
- return v <= 0xFFFFFF ? 'uint32-rgb' : 'uint32-rgba';
- case 'string': {
- const formatInfo = guessStringColorFormat(v.trim());
- if (formatInfo) {
- return formatInfo.format;
- }
- break;
- }
- case 'object':
- if (v instanceof Uint8Array || v instanceof Uint8ClampedArray) {
- if (v.length === 3) {
- return 'uint8-rgb';
- } else if (v.length === 4) {
- return 'uint8-rgba';
- }
- } else if (v instanceof Float32Array) {
- if (v.length === 3) {
- return 'float-rgb';
- } else if (v.length === 4) {
- return 'float-rgba';
- }
- } else if (Array.isArray(v)) {
- if (v.length === 3) {
- return 'float-rgb';
- } else if (v.length === 4) {
- return 'float-rgba';
- }
- } else {
- if ('r' in v && 'g' in v && 'b' in v) {
- if ('a' in v) {
- return 'object-rgba';
- } else {
- return 'object-rgb';
- }
- }
- }
- }
- throw new Error(`unknown color format: ${v}`);
- }
-
- function fixHex6(v) {
- return v.trim(v);
- //const formatInfo = guessStringColorFormat(v.trim());
- //const fix = formatInfo ? formatInfo.fix : v => v;
- //return fix(v.trim());
- }
-
- function fixHex8(v) {
- return v.trim(v);
- //const formatInfo = guessStringColorFormat(v.trim());
- //const fix = formatInfo ? formatInfo.fix : v => v;
- //return fix(v.trim());
- }
-
- function hex6ToHex3(hex6) {
- return (hex6[1] === hex6[2] &&
- hex6[3] === hex6[4] &&
- hex6[5] === hex6[6])
- ? `#${hex6[1]}${hex6[3]}${hex6[5]}`
- : hex6;
- }
-
- const hex3RE = /^(#|)([0-9a-f]{3})$/i;
- function hex3ToHex6(hex3) {
- const m = hex3RE.exec(hex3);
- if (m) {
- const [, , m2] = m;
- return `#${hex3DigitTo6Digit(m2)}`;
- }
- return hex3;
- }
-
- function fixHex3(v) {
- return hex6ToHex3(fixHex6(v));
- }
-
- const strToRGBObject = (s) => {
- try {
- const json = s.replace(/([a-z])/g, '"$1"');
- const rgb = JSON.parse(json);
- if (Number.isNaN(rgb.r) || Number.isNaN(rgb.g) || Number.isNaN(rgb.b)) {
- throw new Error('not {r, g, b}');
- }
- return [true, rgb];
- } catch (e) {
- return [false];
- }
- };
-
- const strToRGBAObject = (s) => {
- try {
- const json = s.replace(/([a-z])/g, '"$1"');
- const rgba = JSON.parse(json);
- if (Number.isNaN(rgba.r) || Number.isNaN(rgba.g) || Number.isNaN(rgba.b) || Number.isNaN(rgba.a)) {
- throw new Error('not {r, g, b, a}');
- }
- return [true, rgba];
- } catch (e) {
- return [false];
- }
- };
-
- const strToCssRGB = s => {
- const m = cssRGBRegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, `rgb(${v.join(', ')})`];
- };
-
- const strToCssRGBA = s => {
- const m = cssRGBARegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map((v, i) => i === 3 ? parseFloat(v) : parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, `rgba(${v.join(', ')})`];
- };
-
- const strToCssHSL = s => {
- const m = cssHSLRegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseFloat(v));
- const outOfRange = v.find(v => Number.isNaN(v));
- return [!outOfRange, `hsl(${v[0]}, ${v[1]}%, ${v[2]}%)`];
- };
-
- const strToCssHSLA = s => {
- const m = cssHSLARegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map(v => parseFloat(v));
- const outOfRange = v.find(v => Number.isNaN(v));
- return [!outOfRange, `hsl(${v[0]} ${v[1]}% ${v[2]}% / ${v[3]})`];
- };
-
- const rgbObjectToStr = rgb => {
- return `{r:${f3(rgb.r)}, g:${f3(rgb.g)}, b:${f3(rgb.b)}}`;
- };
- const rgbaObjectToStr = rgba => {
- return `{r:${f3(rgba.r)}, g:${f3(rgba.g)}, b:${f3(rgba.b)}}, a:${f3(rgba.a)}}`;
- };
-
- const strTo3IntsRE = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/;
- const strTo3Ints = s => {
- const m = strTo3IntsRE.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, v];
- };
-
- const strTo4IntsRE = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/;
- const strTo4Ints = s => {
- const m = strTo4IntsRE.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, v];
- };
-
- const strTo3Floats = s => {
- const numbers = s.split(',').map(s => s.trim());
- const v = numbers.map(v => parseFloat(v));
- if (v.length !== 3) {
- return [false];
- }
- // Note: using isNaN not Number.isNaN
- const badNdx = numbers.findIndex(v => isNaN(v));
- return [badNdx < 0, v.map(v => f3(v))];
- };
-
- const strTo4Floats = s => {
- const numbers = s.split(',').map(s => s.trim());
- const v = numbers.map(v => parseFloat(v));
- if (v.length !== 4) {
- return [false];
- }
- // Note: using isNaN not Number.isNaN
- const badNdx = numbers.findIndex(v => isNaN(v));
- return [badNdx < 0, v.map(v => f3(v))];
- };
-
- const strToUint32RGBRegex = /^\s*(?:0x){0,1}([0-9a-z]{1,6})\s*$/i;
- const strToUint32RGB = s => {
- const m = strToUint32RGBRegex.exec(s);
- if (!m) {
- return [false];
- }
- return [true, parseInt(m[1], 16)];
- };
-
- const strToUint32RGBARegex = /^\s*(?:0x){0,1}([0-9a-z]{1,8})\s*$/i;
- const strToUint32RGBA = s => {
- const m = strToUint32RGBARegex.exec(s);
- if (!m) {
- return [false];
- }
- return [true, parseInt(m[1], 16)];
- };
-
- const hex6RE = /^\s*#[a-f0-9]{6}\s*$|^\s*#[a-f0-9]{3}\s*$/i;
- const hexNoHash6RE = /^\s*[a-f0-9]{6}\s*$/i;
- const hex8RE = /^\s*#[a-f0-9]{8}\s*$/i;
- const hexNoHash8RE = /^\s*[a-f0-9]{8}\s*$/i;
-
- // For each format converter
- //
- // fromHex/toHex convert from/to '#RRGGBB'
- //
- // fromHex converts from the string '#RRBBGG' to the format
- // (eg: for uint32-rgb, '#123456' becomes 0x123456)
- //
- // toHex converts from the format to '#RRGGBB'
- // (eg: for uint8-rgb, [16, 33, 50] becomes '#102132')
- //
- //
- // fromStr/toStr convert from/to what's in the input[type=text] element
- //
- // toStr converts from the format to its string representation
- // (eg, for object-rgb, {r: 1, g: 0.5, b:0} becomes "{r: 1, g: 0.5, b:0}")
- // ^object ^string
- //
- // fromStr converts its string representation to its format
- // (eg, for object-rgb) "{r: 1, g: 0.5, b:0}" becomes {r: 1, g: 0.5, b:0})
- // ^string ^object
- // fromString returns an array which is [valid, v]
- // where valid is true if the string was a valid and v is the converted
- // format if v is true.
- //
- // Note: toStr should convert to "ideal" form (whatever that is).
- // (eg, for css-rgb
- // "{ r: 0.10000, g: 001, b: 0}" becomes "{r: 0.1, g: 1, b: 0}"
- // notice that css-rgb is a string to a string
- // )
- const colorFormatConverters = {
- 'hex6': {
- color: {
- from: v => [true, v],
- to: fixHex6,
- },
- text: {
- from: v => [hex6RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex8': {
- color: {
- from: v => [true, v],
- to: fixHex8,
- },
- text: {
- from: v => [hex8RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex3': {
- color: {
- from: v => [true, fixHex3(v)],
- to: hex3ToHex6,
- },
- text: {
- from: v => [hex6RE.test(v), hex6ToHex3(v.trim())],
- to: v => v,
- },
- },
- 'hex6-no-hash': {
- color: {
- from: v => [true, v.substring(1)],
- to: v => `#${fixHex6(v)}`,
- },
- text: {
- from: v => [hexNoHash6RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex8-no-hash': {
- color: {
- from: v => [true, v.substring(1)],
- to: v => `#${fixHex8(v)}`,
- },
- text: {
- from: v => [hexNoHash8RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex3-no-hash': {
- color: {
- from: v => [true, fixHex3(v).substring(1)],
- to: hex3ToHex6,
- },
- text: {
- from: v => [hexNoHash6RE.test(v), hex6ToHex3(v.trim())],
- to: v => v,
- },
- },
- 'uint32-rgb': {
- color: {
- from: v => [true, hexToUint32RGB(v)],
- to: uint32RGBToHex,
- },
- text: {
- from: v => strToUint32RGB(v),
- to: v => `0x${v.toString(16).padStart(6, '0')}`,
- },
- },
- 'uint32-rgba': {
- color: {
- from: v => [true, hexToUint32RGBA(v)],
- to: uint32RGBAToHex,
- },
- text: {
- from: v => strToUint32RGBA(v),
- to: v => `0x${v.toString(16).padStart(8, '0')}`,
- },
- },
- 'uint8-rgb': {
- color: {
- from: v => [true, hexToUint8RGB(v)],
- to: uint8RGBToHex,
- },
- text: {
- from: strTo3Ints,
- to: v => v.join(', '),
- },
- },
- 'uint8-rgba': {
- color: {
- from: v => [true, hexToUint8RGBA(v)],
- to: uint8RGBAToHex,
- },
- text: {
- from: strTo4Ints,
- to: v => v.join(', '),
- },
- },
- 'float-rgb': {
- color: {
- from: v => [true, hexToFloatRGB(v)],
- to: floatRGBToHex,
- },
- text: {
- from: strTo3Floats,
- // need Array.from because map of Float32Array makes a Float32Array
- to: v => Array.from(v).map(v => f3(v)).join(', '),
- },
- },
- 'float-rgba': {
- color: {
- from: v => [true, hexToFloatRGBA(v)],
- to: floatRGBAToHex,
- },
- text: {
- from: strTo4Floats,
- // need Array.from because map of Float32Array makes a Float32Array
- to: v => Array.from(v).map(v => f3(v)).join(', '),
- },
- },
- 'object-rgb': {
- color: {
- from: v => [true, hexToObjectRGB(v)],
- to: objectRGBToHex,
- },
- text: {
- from: strToRGBObject,
- to: rgbObjectToStr,
- },
- },
- 'object-rgba': {
- color: {
- from: v => [true, hexToObjectRGBA(v)],
- to: objectRGBAToHex,
- },
- text: {
- from: strToRGBAObject,
- to: rgbaObjectToStr,
- },
- },
- 'css-rgb': {
- color: {
- from: v => [true, hexToCssRGB(v)],
- to: cssRGBToHex,
- },
- text: {
- from: strToCssRGB,
- to: v => strToCssRGB(v)[1],
- },
- },
- 'css-rgba': {
- color: {
- from: v => [true, hexToCssRGBA(v)],
- to: cssRGBAToHex,
- },
- text: {
- from: strToCssRGBA,
- to: v => strToCssRGBA(v)[1],
- },
- },
- 'css-hsl': {
- color: {
- from: v => [true, hexToCssHSL(v)],
- to: cssHSLToHex,
- },
- text: {
- from: strToCssHSL,
- to: v => strToCssHSL(v)[1],
- },
- },
- 'css-hsla': {
- color: {
- from: v => [true, hexToCssHSLA(v)],
- to: cssHSLAToHex,
- },
- text: {
- from: strToCssHSLA,
- to: v => strToCssHSLA(v)[1],
- },
- },
- };
-
- class ElementView extends View {
- constructor(tag, className) {
- super(createElem(tag, {className}));
- }
- }
-
- // TODO: remove this? Should just be user side
- class Canvas extends LabelController {
- #canvasElem;
-
- constructor() {
- super('muigui-canvas');
- this.#canvasElem = this.add(
- new ElementView('canvas', 'muigui-canvas'),
- ).domElement;
- }
- get canvas() {
- return this.#canvasElem;
- }
- }
-
- class ColorView extends EditView {
- #to;
- #from;
- #colorElem;
- #skipUpdate;
- #options = {
- converters: identity,
- };
-
- constructor(setter, options) {
- const colorElem = createElem('input', {
- type: 'color',
- onInput: () => {
- const [valid, newV] = this.#from(colorElem.value);
- if (valid) {
- this.#skipUpdate = true;
- setter.setValue(newV);
- }
- },
- onChange: () => {
- const [valid, newV] = this.#from(colorElem.value);
- if (valid) {
- this.#skipUpdate = true;
- setter.setFinalValue(newV);
- }
- },
- });
- super(createElem('div', {}, [colorElem]));
- this.setOptions(options);
- this.#colorElem = colorElem;
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.#colorElem.value = this.#to(v);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {converters: {to, from}} = this.#options;
- this.#to = to;
- this.#from = from;
- return this;
- }
- }
-
- class Color extends ValueController {
- #colorView;
- #textView;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-color');
- const format = options.format || guessFormat(this.getValue());
- const {color, text} = colorFormatConverters[format];
- this.#colorView = this.add(new ColorView(this, {converters: color}));
- this.#textView = this.add(new TextView(this, {converters: text}));
- this.updateDisplay();
- }
- setOptions(options) {
- const {format} = options;
- if (format) {
- const {color, text} = colorFormatConverters[format];
- this.#colorView.setOptions({converters: color});
- this.#textView.setOptions({converters: text});
- }
- super.setOptions(options);
- return this;
- }
- }
-
- // This feels like it should be something else like
- // gui.addController({className: 'muigui-divider')};
- class Divider extends Controller {
- constructor() {
- super('muigui-divider');
- }
- }
-
- class Container extends Controller {
- #controllers;
- #childDestController;
-
- constructor(className) {
- super(className);
- this.#controllers = [];
- this.#childDestController = this;
- }
- get children() {
- return this.#controllers; // should we return a copy?
- }
- get controllers() {
- return this.#controllers.filter(c => !(c instanceof Container));
- }
- get folders() {
- return this.#controllers.filter(c => c instanceof Container);
- }
- reset(recursive = true) {
- for (const controller of this.#controllers) {
- if (!(controller instanceof Container) || recursive) {
- controller.reset(recursive);
- }
- }
- return this;
- }
- updateDisplay() {
- for (const controller of this.#controllers) {
- controller.updateDisplay();
- }
- return this;
- }
- remove(controller) {
- const ndx = this.#controllers.indexOf(controller);
- if (ndx >= 0) {
- const c = this.#controllers.splice(ndx, 1);
- const c0 = c[0];
- const elem = c0.domElement;
- elem.remove();
- c0.setParent(null);
- }
- return this;
- }
- _addControllerImpl(controller) {
- this.domElement.appendChild(controller.domElement);
- this.#controllers.push(controller);
- controller.setParent(this);
- return controller;
- }
- addController(controller) {
- return this.#childDestController._addControllerImpl(controller);
- }
- pushContainer(container) {
- this.addController(container);
- this.#childDestController = container;
- return container;
- }
- popContainer() {
- this.#childDestController = this.#childDestController.parent;
- return this;
- }
- }
-
- class Folder extends Container {
- #labelElem;
-
- constructor(name = 'Controls', className = 'muigui-menu') {
- super(className);
- this.#labelElem = createElem('label');
- this.addElem(createElem('button', {
- type: 'button',
- onClick: () => this.toggleOpen(),
- }, [this.#labelElem]));
- this.pushContainer(new Container());
- this.name(name);
- this.open();
- }
- open(open = true) {
- this.domElement.classList.toggle('muigui-closed', !open);
- this.domElement.classList.toggle('muigui-open', open);
- return this;
- }
- close() {
- return this.open(false);
- }
- name(name) {
- this.#labelElem.textContent = name;
- return this;
- }
- title(title) {
- return this.name(title);
- }
- toggleOpen() {
- this.open(!this.domElement.classList.contains('muigui-open'));
- return this;
- }
- }
-
- // This feels like it should be something else like
- // gui.addDividing = new Controller()
- class Label extends Controller {
- constructor(text) {
- super('muigui-label');
- this.text(text);
- }
- text(text) {
- this.domElement.textContent = text;
- return this;
- }
- }
-
- function noop$1() {
- }
-
- function computeRelativePosition(elem, event, start) {
- const rect = elem.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- const nx = x / rect.width;
- const ny = y / rect.height;
- start = start || [x, y];
- const dx = x - start[0];
- const dy = y - start[1];
- const ndx = dx / rect.width;
- const ndy = dy / rect.width;
- return {x, y, nx, ny, dx, dy, ndx, ndy};
- }
-
- function addTouchEvents(elem, {onDown = noop$1, onMove = noop$1, onUp = noop$1}) {
- let start;
- const pointerMove = function(event) {
- const e = {
- type: 'move',
- ...computeRelativePosition(elem, event, start),
- };
- onMove(e);
- };
-
- const pointerUp = function(event) {
- elem.releasePointerCapture(event.pointerId);
- elem.removeEventListener('pointermove', pointerMove);
- elem.removeEventListener('pointerup', pointerUp);
-
- document.body.style.backgroundColor = '';
-
- onUp('up');
- };
-
- const pointerDown = function(event) {
- elem.addEventListener('pointermove', pointerMove);
- elem.addEventListener('pointerup', pointerUp);
- elem.setPointerCapture(event.pointerId);
-
- const rel = computeRelativePosition(elem, event);
- start = [rel.x, rel.y];
- onDown({
- type: 'down',
- ...rel,
- });
- };
-
- elem.addEventListener('pointerdown', pointerDown);
-
- return function() {
- elem.removeEventListener('pointerdown', pointerDown);
- };
- }
-
- const svg$3 = `
-
-
-
-`;
-
- function connectFillTargets(elem) {
- elem.querySelectorAll('[data-src]').forEach(srcElem => {
- const id = getNewId();
- srcElem.id = id;
- elem.querySelectorAll(`[data-target=${srcElem.dataset.src}]`).forEach(targetElem => {
- targetElem.setAttribute('fill', `url(#${id})`);
- });
- });
- return elem;
- }
-
- // Was originally going to make alpha an option. Issue is
- // hard coded conversions?
- class ColorChooserView extends EditView {
- #to;
- #from;
- #satLevelElem;
- #circleElem;
- #hueUIElem;
- #hueElem;
- #hueCursorElem;
- #alphaUIElem;
- #alphaElem;
- #alphaCursorElem;
- #hsva;
- #skipHueUpdate;
- #skipSatLevelUpdate;
- #skipAlphaUpdate;
- #options = {
- converters: identity,
- alpha: false,
- };
- #convertInternalToHex;
- #convertHexToInternal;
-
- constructor(setter, options) {
- super(createElem('div', {
- innerHTML: svg$3,
- className: 'muigui-no-scroll',
- }));
- this.#satLevelElem = this.domElement.children[0];
- this.#hueUIElem = this.domElement.children[1];
- this.#alphaUIElem = this.domElement.children[2];
- connectFillTargets(this.#satLevelElem);
- connectFillTargets(this.#hueUIElem);
- connectFillTargets(this.#alphaUIElem);
- this.#circleElem = this.$('.muigui-color-chooser-circle');
- this.#hueElem = this.$('[data-src=muigui-color-chooser-hue]');
- this.#hueCursorElem = this.$('.muigui-color-chooser-hue-cursor');
- this.#alphaElem = this.$('[data-src=muigui-color-chooser-alpha]');
- this.#alphaCursorElem = this.$('.muigui-color-chooser-alpha-cursor');
-
- const handleSatLevelChange = (e) => {
- const s = clamp$1(e.nx, 0, 1);
- const v = clamp$1(e.ny, 0, 1);
- this.#hsva[1] = s;
- this.#hsva[2] = (1 - v);
- this.#skipHueUpdate = true;
- this.#skipAlphaUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- const handleHueChange = (e) => {
- const h = clamp$1(e.nx, 0, 1);
- this.#hsva[0] = h;
- this.#skipSatLevelUpdate = true;
- this.#skipAlphaUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- const handleAlphaChange = (e) => {
- const a = clamp$1(e.nx, 0, 1);
- this.#hsva[3] = a;
- this.#skipHueUpdate = true;
- this.#skipSatLevelUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- addTouchEvents(this.#satLevelElem, {
- onDown: handleSatLevelChange,
- onMove: handleSatLevelChange,
- });
- addTouchEvents(this.#hueUIElem, {
- onDown: handleHueChange,
- onMove: handleHueChange,
- });
- addTouchEvents(this.#alphaUIElem, {
- onDown: handleAlphaChange,
- onMove: handleAlphaChange,
- });
- this.setOptions(options);
- }
- updateDisplay(newV) {
- if (!this.#hsva) {
- this.#hsva = this.#convertHexToInternal(this.#to(newV));
- }
- {
- const [h, s, v, a = 1] = this.#convertHexToInternal(this.#to(newV));
- // Don't copy the hue if it was un-computable.
- if (!this.#skipHueUpdate) {
- this.#hsva[0] = s > 0.001 && v > 0.001 ? h : this.#hsva[0];
- }
- if (!this.#skipSatLevelUpdate) {
- this.#hsva[1] = s;
- this.#hsva[2] = v;
- }
- if (!this.#skipAlphaUpdate) {
- this.#hsva[3] = a;
- }
- }
- {
- const [h, s, v, a] = this.#hsva;
- const [hue, sat, lum] = rgbaFloatToHsla01(hsva01ToRGBAFloat(this.#hsva));
-
- if (!this.#skipHueUpdate) {
- this.#hueCursorElem.setAttribute('transform', `translate(${h * 64}, 0)`);
- }
- this.#hueElem.children[0].setAttribute('stop-color', `hsl(${hue * 360} 0% 100% / ${a})`);
- this.#hueElem.children[1].setAttribute('stop-color', `hsl(${hue * 360} 100% 50% / ${a})`);
- if (!this.#skipAlphaUpdate) {
- this.#alphaCursorElem.setAttribute('transform', `translate(${a * 64}, 0)`);
- }
- this.#alphaElem.children[0].setAttribute('stop-color', `hsl(${hue * 360} ${sat * 100}% ${lum * 100}% / 0)`);
- this.#alphaElem.children[1].setAttribute('stop-color', `hsl(${hue * 360} ${sat * 100}% ${lum * 100}% / 1)`);
-
- if (!this.#skipSatLevelUpdate) {
- this.#circleElem.setAttribute('cx', `${s * 64}`);
- this.#circleElem.setAttribute('cy', `${(1 - v) * 48}`);
- }
- }
- this.#skipHueUpdate = false;
- this.#skipSatLevelUpdate = false;
- this.#skipAlphaUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {converters: {to, from}, alpha} = this.#options;
- this.#alphaUIElem.style.display = alpha ? '' : 'none';
- this.#convertInternalToHex = alpha
- ? v => floatRGBAToHex(hsva01ToRGBAFloat(v))
- : v => floatRGBToHex(hsv01ToRGBFloat(v));
- this.#convertHexToInternal = alpha
- ? v => rgbaFloatToHSVA01(hexToFloatRGBA(v))
- : v => rgbFloatToHSV01(hexToFloatRGB(v));
- this.#to = to;
- this.#from = from;
- return this;
- }
- }
-
- /*
-
- holder = new TabHolder
- tab = holder.add(new Tab("name"))
- tab.add(...)
-
-
- pc = new PopdownController
- top = pc.add(new Row())
- top.add(new Button());
- values = topRow.add(new Div())
- bottom = pc.add(new Row());
-
-
-
- pc = new PopdownController
- pc.addTop
- pc.addTop
-
- pc.addBottom
-
-
- */
-
- class PopDownController extends ValueController {
- #top;
- #valuesView;
- #checkboxElem;
- #bottom;
- #options = {
- open: false,
- };
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-pop-down-controller');
- /*
- [ValueView
- [[B][values]] upper row
- [[ visual ]] lower row
- ]
- */
- this.#top = this.add(new ElementView('div', 'muigui-pop-down-top'));
- // this.#top.add(new CheckboxView(makeSetter(this.#options, 'open')));
- const checkboxElem = this.#top.addElem(createElem('input', {
- type: 'checkbox',
- onChange: () => {
- this.#options.open = checkboxElem.checked;
- this.updateDisplay();
- },
- }));
- this.#checkboxElem = checkboxElem;
- this.#valuesView = this.#top.add(new ElementView('div', 'muigui-pop-down-values'));
- this.#bottom = this.add(new ElementView('div', 'muigui-pop-down-bottom'));
- this.setOptions(options);
- }
- setKnobColor(bgCssColor/*, fgCssColor*/) {
- if (this.#checkboxElem) {
- this.#checkboxElem.style = `
- --range-color: ${bgCssColor};
- --value-bg-color: ${bgCssColor};
- `;
- }
- }
- updateDisplay() {
- super.updateDisplay();
- const {open} = this.#options;
- this.domElement.children[1].classList.toggle('muigui-open', open);
- this.domElement.children[1].classList.toggle('muigui-closed', !open);
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- super.setOptions(options);
- this.updateDisplay();
- }
- addTop(view) {
- return this.#valuesView.add(view);
- }
- addBottom(view) {
- return this.#bottom.add(view);
- }
- }
-
- class ColorChooser extends PopDownController {
- #colorView;
- #textView;
- #to;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-color-chooser');
- const format = options.format || guessFormat(this.getValue());
- const {color, text} = colorFormatConverters[format];
- this.#to = color.to;
- this.#textView = new TextView(this, {converters: text, alpha: hasAlpha(format)});
- this.#colorView = new ColorChooserView(this, {converters: color, alpha: hasAlpha(format)});
- this.addTop(this.#textView);
- this.addBottom(this.#colorView);
- // WTF! FIX!
- this.__setKnobHelper = () => {
- if (this.#to) {
- const hex6Or8 = this.#to(this.getValue());
- const hsl = rgbUint8ToHsl(hexToUint8RGB(hex6Or8));
- hsl[2] = (hsl[2] + 50) % 100;
- const hex = uint8RGBToHex(hslToRgbUint8(hsl));
- this.setKnobColor(`${hex6Or8.substring(0, 7)}FF`, hex);
- }
- };
- this.updateDisplay();
- }
- updateDisplay() {
- super.updateDisplay();
- if (this.__setKnobHelper) {
- this.__setKnobHelper();
- }
- }
- setOptions(options) {
- super.setOptions(options);
- return this;
- }
- }
-
- function showCSS(ob) {
- if (ob.prototype.css) {
- showCSS(ob.prototype);
- }
- }
-
- class Layout extends View {
- static css = 'bar';
- constructor(tag, className) {
- super(createElem(tag, {className}));
-
- showCSS(this);
- }
- }
-
- /*
- class ValueController ?? {
- const row = this.add(new Row());
- const label = row.add(new Label());
- const div = row.add(new Div());
- const row = div.add(new Row());
- }
- */
-
- /*
- class MyCustomThing extends ValueController {
- constructor(object, property, options) {
- const topRow = this.add(new Row());
- const bottomRow = this.add(new Row());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- }
- }
- new Grid([
- [new
- ]
- */
-
- class Column extends Layout {
- constructor() {
- super('div', 'muigui-row');
- }
- }
-
- class Frame extends Layout {
- static css = 'foo';
- constructor() {
- super('div', 'muigui-frame');
- }
- static get foo() {
- return 'boo';
- }
- }
-
- class Grid extends Layout {
- constructor() {
- super('div', 'muigui-grid');
- }
- }
-
- class Row extends Layout {
- constructor() {
- super('div', 'muigui-row');
- }
- }
-
- class GUIFolder extends Folder {
- add(object, property, ...args) {
- const controller = object instanceof Controller
- ? object
- : createController(object, property, ...args);
- return this.addController(controller);
- }
- addCanvas(name) {
- return this.addController(new Canvas(name));
- }
- addColor(object, property, options = {}) {
- const value = object[property];
- if (hasAlpha(options.format || guessFormat(value))) {
- return this.addController(new ColorChooser(object, property, options));
- } else {
- return this.addController(new Color(object, property, options));
- }
- }
- addDivider() {
- return this.addController(new Divider());
- }
- addFolder(name) {
- return this.addController(new GUIFolder(name));
- }
- addLabel(text) {
- return this.addController(new Label(text));
- }
- }
-
- class MuiguiElement extends HTMLElement {
- constructor() {
- super();
- this.shadow = this.attachShadow({mode: 'open'});
- }
- }
-
- customElements.define('muigui-element', MuiguiElement);
-
- const baseStyleSheet = new CSSStyleSheet();
- baseStyleSheet.replaceSync(css.default);
- const userStyleSheet = new CSSStyleSheet();
-
- function makeStyleSheetUpdater(styleSheet) {
- let newCss;
- let newCssPromise;
-
- function updateStyle() {
- if (newCss && !newCssPromise) {
- const s = newCss;
- newCss = undefined;
- newCssPromise = styleSheet.replace(s).then(() => {
- newCssPromise = undefined;
- updateStyle();
- });
- }
- }
-
- return function updateStyleSheet(css) {
- newCss = css;
- updateStyle();
- };
- }
-
- const updateBaseStyle = makeStyleSheetUpdater(baseStyleSheet);
- const updateUserStyle = makeStyleSheetUpdater(userStyleSheet);
-
- class GUI extends GUIFolder {
- static converters = converters;
- static mapRange = mapRange;
- static makeRangeConverters = makeRangeConverters;
- static makeRangeOptions = makeRangeOptions;
- static makeMinMaxPair = makeMinMaxPair;
- #localStyleSheet = new CSSStyleSheet();
-
- constructor(options = {}) {
- super('Controls', 'muigui-root');
- if (options instanceof HTMLElement) {
- options = {parent: options};
- }
- const {
- autoPlace = true,
- width,
- title = 'Controls',
- } = options;
- let {
- parent,
- } = options;
-
- if (width) {
- this.domElement.style.width = /^\d+$/.test(width) ? `${width}px` : width;
- }
- if (parent === undefined && autoPlace) {
- parent = document.body;
- this.domElement.classList.add('muigui-auto-place');
- }
- if (parent) {
- const muiguiElement = createElem('muigui-element');
- muiguiElement.shadowRoot.adoptedStyleSheets = [baseStyleSheet, userStyleSheet, this.#localStyleSheet];
- muiguiElement.shadow.appendChild(this.domElement);
- parent.appendChild(muiguiElement);
- }
- if (title) {
- this.title(title);
- }
- this.domElement.classList.add('muigui', 'muigui-colors');
- }
- setStyle(css) {
- this.#localStyleSheet.replace(css);
- }
- static setBaseStyles(css) {
- updateBaseStyle(css);
- }
- static getBaseStyleSheet() {
- return baseStyleSheet;
- }
- static setUserStyles(css) {
- updateUserStyle(css);
- }
- static getUserStyleSheet() {
- return userStyleSheet;
- }
- static setTheme(name) {
- GUI.setBaseStyles(`${css.default}\n${css.themes[name] || ''}`);
- }
- }
-
- function noop() {
- }
-
- const keyDirections = {
- ArrowLeft: [-1, 0],
- ArrowRight: [1, 0],
- ArrowUp: [0, -1],
- ArrowDown: [0, 1],
- };
-
- // This probably needs to be global
- function addKeyboardEvents(elem, {onDown = noop, onUp = noop}) {
- const keyDown = function(event) {
- const mult = event.shiftKey ? 10 : 1;
- const [dx, dy] = (keyDirections[event.key] || [0, 0]).map(v => v * mult);
- const fn = event.type === 'keydown' ? onDown : onUp;
- fn({
- type: event.type.substring(3),
- dx,
- dy,
- event,
- });
- };
-
- elem.addEventListener('keydown', keyDown);
- elem.addEventListener('keyup', keyDown);
-
- return function() {
- elem.removeEventListener('keydown', keyDown);
- elem.removeEventListener('keyup', keyDown);
- };
- }
-
- function assert(truthy, msg = '') {
- if (!truthy) {
- throw new Error(msg);
- }
- }
-
- function getEllipsePointForAngle(cx, cy, rx, ry, phi, theta) {
- const m = Math.abs(rx) * Math.cos(theta);
- const n = Math.abs(ry) * Math.sin(theta);
-
- return [
- cx + Math.cos(phi) * m - Math.sin(phi) * n,
- cy + Math.sin(phi) * m + Math.cos(phi) * n,
- ];
- }
-
- function getEndpointParameters(cx, cy, rx, ry, phi, theta, dTheta) {
- const [x1, y1] = getEllipsePointForAngle(cx, cy, rx, ry, phi, theta);
- const [x2, y2] = getEllipsePointForAngle(cx, cy, rx, ry, phi, theta + dTheta);
-
- const fa = Math.abs(dTheta) > Math.PI ? 1 : 0;
- const fs = dTheta > 0 ? 1 : 0;
-
- return { x1, y1, x2, y2, fa, fs };
- }
-
- function arc(cx, cy, r, start, end) {
- assert(Math.abs(start - end) <= Math.PI * 2);
- assert(start >= -Math.PI && start <= Math.PI * 2);
- assert(start <= end);
- assert(end >= -Math.PI && end <= Math.PI * 4);
-
- const { x1, y1, x2, y2, fa, fs } = getEndpointParameters(cx, cy, r, r, 0, start, end - start);
- return Math.abs(Math.abs(start - end) - Math.PI * 2) > Number.EPSILON
- ? `M${cx} ${cy} L${x1} ${y1} A ${r} ${r} 0 ${fa} ${fs} ${x2} ${y2} L${cx} ${cy}`
- : `M${x1} ${y1} L${x1} ${y1} A ${r} ${r} 0 ${fa} ${fs} ${x2} ${y2}`;
- }
-
- const svg$2 = `
-
-`;
-
- const twoPiMod = v => euclideanModulo$1(v + Math.PI, Math.PI * 2) - Math.PI;
-
- class DirectionView extends EditView {
- #arrowElem;
- #rangeElem;
- #lastV;
- #wrap;
- #options = {
- step: 1,
- min: -180,
- max: 180,
-
- /*
- --------
- / -ฯ/2 \
- / | \
- |<- -ฯ * |
- | * 0 ->| zero is down the positive X axis
- |<- +ฯ * |
- \ | /
- \ ฯ/2 /
- --------
- */
- dirMin: -Math.PI,
- dirMax: Math.PI,
- //dirMin: Math.PI * 0.5,
- //dirMax: Math.PI * 2.5,
- //dirMin: -Math.PI * 0.75, // test 10:30 to 7:30
- //dirMax: Math.PI * 0.75,
- //dirMin: Math.PI * 0.75, // test 7:30 to 10:30
- //dirMax: -Math.PI * 0.75,
- //dirMin: -Math.PI * 0.75, // test 10:30 to 1:30
- //dirMax: -Math.PI * 0.25,
- //dirMin: Math.PI * 0.25, // test 4:30 to 7:30
- //dirMax: Math.PI * 0.75,
- //dirMin: Math.PI * 0.75, // test 4:30 to 7:30
- //dirMax: Math.PI * 0.25,
- wrap: undefined,
- converters: identity,
- };
-
- constructor(setter, options = {}) {
- const wheelHelper = createWheelHelper();
- super(createElem('div', {
- className: 'muigui-direction muigui-no-scroll',
- innerHTML: svg$2,
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- let tempV = this.#lastV + delta;
- if (this.#wrap) {
- tempV = euclideanModulo$1(tempV - min, max - min) + min;
- }
- const newV = clamp$1(stepify(tempV, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- const handleTouch = (e) => {
- const {min, max, step, dirMin, dirMax} = this.#options;
- const nx = e.nx * 2 - 1;
- const ny = e.ny * 2 - 1;
- const a = Math.atan2(ny, nx);
-
- const center = (dirMin + dirMax) / 2;
-
- const centeredAngle = twoPiMod(a - center);
- const centeredStart = twoPiMod(dirMin - center);
- const diff = dirMax - dirMin;
-
- const n = clamp$1((centeredAngle - centeredStart) / (diff), 0, 1);
- const newV = stepify(min + (max - min) * n, v => v, step);
- setter.setValue(newV);
- };
- addTouchEvents(this.domElement, {
- onDown: handleTouch,
- onMove: handleTouch,
- });
- addKeyboardEvents(this.domElement, {
- onDown: (e) => {
- const {min, max, step} = this.#options;
- const newV = clamp$1(stepify(this.#lastV + e.dx * step, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- this.#arrowElem = this.$('#muigui-arrow');
- this.#rangeElem = this.$('#muigui-range');
- this.setOptions(options);
- }
- updateDisplay(v) {
- this.#lastV = v;
- const {min, max} = this.#options;
- const n = (v - min) / (max - min);
- const angle = lerp$1(this.#options.dirMin, this.#options.dirMax, n);
- this.#arrowElem.style.transform = `rotate(${angle}rad)`;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {dirMin, dirMax, wrap} = this.#options;
- this.#wrap = wrap !== undefined
- ? wrap
- : Math.abs(dirMin - dirMax) >= Math.PI * 2 - Number.EPSILON;
- const [min, max] = dirMin < dirMax ? [dirMin, dirMax] : [dirMax , dirMin];
- this.#rangeElem.setAttribute('d', arc(0, 0, 28.87, min, max));
- }
- }
-
- // deg2rad
- // where is 0
- // range (0, 360), (-180, +180), (0,0) Really this is a range
-
- class Direction extends PopDownController {
- #options;
- constructor(object, property, options) {
- super(object, property, 'muigui-direction');
- this.#options = options; // FIX
- this.addTop(new NumberView(this,
- identity));
- this.addBottom(new DirectionView(this, options));
- this.updateDisplay();
- }
- }
-
- class RadioGridView extends EditView {
- #values;
-
- constructor(setter, keyValues, cols = 3) {
- const values = [];
- const name = makeId();
- super(createElem('div', {}, keyValues.map(([key, value], ndx) => {
- values.push(value);
- return createElem('label', {}, [
- createElem('input', {
- type: 'radio',
- name,
- value: ndx,
- onChange: function() {
- if (this.checked) {
- setter.setFinalValue(that.#values[this.value]);
- }
- },
- }),
- createElem('button', {
- type: 'button',
- textContent: key,
- onClick: function() {
- this.previousElementSibling.click();
- },
- }),
- ]);
- })));
- const that = this;
- this.#values = values;
- this.cols(cols);
- }
- updateDisplay(v) {
- const ndx = this.#values.indexOf(v);
- for (let i = 0; i < this.domElement.children.length; ++i) {
- this.domElement.children[i].children[0].checked = i === ndx;
- }
- }
- cols(cols) {
- this.domElement.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
- }
- }
-
- class RadioGrid extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-radio-grid');
- const valueIsNumber = typeof this.getValue() === 'number';
- const {
- keyValues: keyValuesInput,
- cols = 3,
- } = options;
- const keyValues = convertToKeyValues(keyValuesInput, valueIsNumber);
- this.add(new RadioGridView(this, keyValues, cols));
- this.updateDisplay();
- }
- }
-
- function onResize(elem, callback) {
- new ResizeObserver(() => {
- callback({rect: elem.getBoundingClientRect(), elem});
- }).observe(elem);
- }
-
- function onResizeSVGNoScale(elem, hAnchor, vAnchor, callback) {
- onResize(elem, ({rect}) => {
- const {width, height} = rect;
- elem.setAttribute('viewBox', `-${width * hAnchor} -${height * vAnchor} ${width} ${height}`);
- callback({elem, rect});
- });
- }
-
- function onResizeCanvas(elem, callback) {
- onResize(elem, ({rect}) => {
- const {width, height} = rect;
- elem.width = width;
- elem.height = height;
- callback({elem, rect});
- });
- }
-
- const svg$1 = `
-
-`;
-
- function createSVGTicks(start, end, step, min, max, height) {
- const p = [];
- if (start < min) {
- start += stepify(min - start, v => v, step);
- }
- end = Math.min(end, max);
- for (let i = start; i <= end; i += step) {
- p.push(`M${i} 0 l0 ${height}`);
- }
- return p.join(' ');
- }
-
- function createSVGNumbers(start, end, unitSize, unit, minusSize, min, max, labelFn) {
- const texts = [];
- if (start < min) {
- start += stepify(min - start, v => v, unitSize);
- }
- end = Math.min(end, max);
- const digits = Math.max(0, -Math.log10(unit));
- const f = v => labelFn(v.toFixed(digits));
- for (let i = start; i <= end; i += unitSize) {
- texts.push(`${f(i / unitSize * unit)}`);
- }
- return texts.join('\n');
- }
-
- function computeSizeOfMinus(elem) {
- const oldHTML = elem.innerHTML;
- elem.innerHTML = '- ';
- const text = elem.querySelector('text');
- const size = text.getComputedTextLength();
- elem.innerHTML = oldHTML;
- return size;
- }
-
- class SliderView extends EditView {
- #svgElem;
- #originElem;
- #ticksElem;
- #thicksElem;
- #numbersElem;
- #leftGradElem;
- #rightGradElem;
- #width;
- #height;
- #lastV;
- #minusSize;
- #options = {
- min: -100,
- max: 100,
- step: 1,
- unit: 10,
- unitSize: 10,
- ticksPerUnit: 5,
- labelFn: v => v,
- tickHeight: 1,
- limits: true,
- thicksColor: undefined,
- orientation: undefined,
- };
-
- constructor(setter, options) {
- const wheelHelper = createWheelHelper();
- super(createElem('div', {
- innerHTML: svg$1,
- className: 'muigui-no-v-scroll',
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const newV = clamp$1(stepify(this.#lastV + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.#svgElem = this.$('svg');
- this.#originElem = this.$('#muigui-origin');
- this.#ticksElem = this.$('#muigui-ticks');
- this.#thicksElem = this.$('#muigui-thicks');
- this.#numbersElem = this.$('#muigui-numbers');
- this.#leftGradElem = this.$('#muigui-left-grad');
- this.#rightGradElem = this.$('#muigui-right-grad');
- this.setOptions(options);
- let startV;
- addTouchEvents(this.domElement, {
- onDown: () => {
- startV = this.#lastV;
- },
- onMove: (e) => {
- const {min, max, unitSize, unit, step} = this.#options;
- const newV = clamp$1(stepify(startV - e.dx / unitSize * unit, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- addKeyboardEvents(this.domElement, {
- onDown: (e) => {
- const {min, max, step} = this.#options;
- const newV = clamp$1(stepify(this.#lastV + e.dx * step, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- onResizeSVGNoScale(this.#svgElem, 0.5, 0, ({rect: {width}}) => {
- this.#leftGradElem.setAttribute('x', -width / 2);
- this.#rightGradElem.setAttribute('x', width / 2 - 20);
- this.#minusSize = computeSizeOfMinus(this.#numbersElem);
- this.#width = width;
- this.#updateSlider();
- });
- }
- // |--------V--------|
- // . . | . . . | . . . |
- //
- #updateSlider() {
- // There's no size if ResizeObserver has not fired yet.
- if (!this.#width || this.#lastV === undefined) {
- return;
- }
- const {
- labelFn,
- limits,
- min,
- max,
- orientation,
- tickHeight,
- ticksPerUnit,
- unit,
- unitSize,
- thicksColor,
- } = this.#options;
- const unitsAcross = Math.ceil(this.#width / unitSize);
- const center = this.#lastV;
- const centerUnitSpace = center / unit;
- const startUnitSpace = Math.round(centerUnitSpace - unitsAcross);
- const endUnitSpace = startUnitSpace + unitsAcross * 2;
- const start = startUnitSpace * unitSize;
- const end = endUnitSpace * unitSize;
- const minUnitSpace = limits ? min * unitSize / unit : start;
- const maxUnitSpace = limits ? max * unitSize / unit : end;
- const height = labelFn(1) === '' ? 10 : 5;
- if (ticksPerUnit > 1) {
- this.#ticksElem.setAttribute('d', createSVGTicks(start, end, unitSize / ticksPerUnit, minUnitSpace, maxUnitSpace, height * tickHeight));
- }
- this.#thicksElem.style.stroke = thicksColor; //setAttribute('stroke', thicksColor);
- this.#thicksElem.setAttribute('d', createSVGTicks(start, end, unitSize, minUnitSpace, maxUnitSpace, height));
- this.#numbersElem.innerHTML = createSVGNumbers(start, end, unitSize, unit, this.#minusSize, minUnitSpace, maxUnitSpace, labelFn);
- this.#originElem.setAttribute('transform', `translate(${-this.#lastV * unitSize / unit} 0)`);
- this.#svgElem.classList.toggle('muigui-slider-up', orientation === 'up');
- }
- updateDisplay(v) {
- this.#lastV = v;
- this.#updateSlider();
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- return this;
- }
- }
-
- class Slider extends ValueController {
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-slider');
- this.add(new SliderView(this, options));
- this.add(new NumberView(this, options));
- this.updateDisplay();
- }
- }
-
- const svg = `
-
-`;
-
- class Vec2View extends EditView {
- #svgElem;
- #arrowElem;
- #circleElem;
- #lastV = [];
-
- constructor(setter) {
- super(createElem('div', {
- innerHTML: svg,
- className: 'muigui-no-scroll',
- }));
- const onTouch = (e) => {
- const {width, height} = this.#svgElem.getBoundingClientRect();
- const nx = e.nx * 2 - 1;
- const ny = e.ny * 2 - 1;
- setter.setValue([nx * width * 0.5, ny * height * 0.5]);
- };
- addTouchEvents(this.domElement, {
- onDown: onTouch,
- onMove: onTouch,
- });
- this.#svgElem = this.$('svg');
- this.#arrowElem = this.$('#muigui-arrow');
- this.#circleElem = this.$('#muigui-circle');
- onResizeSVGNoScale(this.#svgElem, 0.5, 0.5, () => this.#updateDisplayImpl);
- }
- #updateDisplayImpl() {
- const [x, y] = this.#lastV;
- this.#arrowElem.setAttribute('d', `M0,0L${x},${y}`);
- this.#circleElem.setAttribute('transform', `translate(${x}, ${y})`);
- }
- updateDisplay(v) {
- this.#lastV[0] = v[0];
- this.#lastV[1] = v[1];
- this.#updateDisplayImpl();
- }
- }
-
- // TODO: zoom with wheel and pinch?
- // TODO: grid?
- // // options
- // scale:
- // range: number (both x and y + /)
- // range: array (min, max)
- // xRange:
- // deg/rad/turn
-
- class Vec2 extends PopDownController {
- constructor(object, property) {
- super(object, property, 'muigui-vec2');
-
- const makeSetter = (ndx) => {
- return {
- setValue: (v) => {
- const newV = this.getValue();
- newV[ndx] = v;
- this.setValue(newV);
- },
- setFinalValue: (v) => {
- const newV = this.getValue();
- newV[ndx] = v;
- this.setFinalValue(newV);
- },
- };
- };
-
- this.addTop(new NumberView(makeSetter(0), {
- converters: {
- to: v => v[0],
- from: strToNumber.from,
- },
- }));
- this.addTop(new NumberView(makeSetter(1), {
- converters: {
- to: v => v[1],
- from: strToNumber.from,
- },
- }));
- this.addBottom(new Vec2View(this));
- this.updateDisplay();
- }
- }
-
- GUI.ColorChooser = ColorChooser;
- GUI.Direction = Direction;
- GUI.RadioGrid = RadioGrid;
- GUI.Range = Range;
- GUI.Select = Select;
- GUI.Slider = Slider;
- GUI.TextNumber = TextNumber;
- GUI.Vec2 = Vec2;
-
- return GUI;
-
-}));
-//# sourceMappingURL=muigui.js.map
diff --git a/dist/0.x/muigui.min.js b/dist/0.x/muigui.min.js
deleted file mode 100644
index 9a859d1..0000000
--- a/dist/0.x/muigui.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).GUI=e()}(this,(function(){"use strict";var t={default:'\n.muigui {\n --bg-color: #ddd;\n --color: #222;\n --contrast-color: #eee;\n --value-color: #145 ;\n --value-bg-color: #eeee;\n --disabled-color: #999;\n --menu-bg-color: #f8f8f8;\n --menu-sep-color: #bbb;\n --hover-bg-color: #999;\n --focus-color: #68C;\n --range-color: #888888;\n --invalid-color: #FF0000;\n --selected-color: rgb(255, 255, 255, 0.9);\n\n --button-bg-color: var(--value-bg-color);\n\n --range-left-color: var(--value-color);\n --range-right-color: var(--value-bg-color); \n --range-right-hover-color: var(--hover-bg-color);\n\n color: var(--color);\n background-color: var(--bg-color);\n}\n\n@media (prefers-color-scheme: dark) {\n .muigui {\n --bg-color: #222222;\n --color: #dddddd;\n --contrast-color: #000;\n --value-color: #43e5f7;\n --value-bg-color: #444444;\n --disabled-color: #666666;\n --menu-bg-color: #080808;\n --menu-sep-color: #444444;\n --hover-bg-color: #666666;\n --focus-color: #88AAFF;\n --range-color: #888888;\n --invalid-color: #FF6666;\n --selected-color: rgba(255, 255, 255, 0.3);\n\n --button-bg-color: var(--value-bg-color);\n\n --range-left-color: var(--value-color);\n --range-right-color: var(--value-bg-color); \n --range-right-hover-color: var(--hover-bg-color);\n\n color: var(--color);\n background-color: var(--bg-color);\n }\n}\n\n.muigui {\n --width: 250px;\n --label-width: 45%;\n --number-width: 40%;\n\n\n --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;\n --font-size: 11px;\n --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace;\n --font-size-mono: 11px;\n\n --line-height: 1.7em;\n --border-radius: 0px;\n\n width: var(--width);\n font-family: var(--font-family);\n font-size: var(--font-size);\n box-sizing: border-box;\n line-height: 100%;\n}\n.muigui * {\n box-sizing: inherit;\n}\n\n.muigui-no-scroll {\n touch-action: none;\n}\n.muigui-no-h-scroll {\n touch-action: pan-y;\n}\n.muigui-no-v-scroll {\n touch-action: pan-x;\n}\n\n.muigui-invalid-value {\n background-color: red !important;\n color: white !important;\n}\n\n.muigui-grid {\n display: grid;\n}\n.muigui-rows {\n display: flex;\n flex-direction: column;\n\n min-height: 20px;\n border: 2px solid red;\n}\n.muigui-columns {\n display: flex;\n flex-direction: row;\n\n height: 20px;\n border: 2px solid green;\n}\n.muigui-rows>*,\n.muigui-columns>* {\n flex: 1 1 auto;\n align-items: stretch;\n min-height: 0;\n min-width: 0;\n}\n\n.muigui-row {\n border: 2px solid yellow;\n min-height: 10px\n}\n.muigui-column {\n border: 2px solid lightgreen;\n}\n\n/* -------- */\n\n.muigui-show { /* */ }\n.muigui-hide { \n display: none !important;\n}\n.muigui-disabled {\n pointer-events: none;\n --color: var(--disabled-color) !important;\n --value-color: var(--disabled-color) !important;\n --range-left-color: var(--disabled-color) !important;\n}\n\n.muigui canvas,\n.muigui svg {\n display: block;\n border-radius: var(--border-radius);\n}\n.muigui canvas {\n background-color: var(--value-bg-color);\n}\n\n.muigui-controller {\n min-width: 0;\n min-height: var(--line-height);\n}\n.muigui-root,\n.muigui-menu {\n display: flex;\n flex-direction: column;\n position: relative;\n user-select: none;\n height: fit-content;\n margin: 0;\n padding-bottom: 0.1em;\n border-radius: var(--border-radius);\n}\n.muigui-menu {\n border-bottom: 1px solid var(--menu-sep-color);\n}\n\n.muigui-root>button:nth-child(1),\n.muigui-menu>button:nth-child(1) {\n border-top: 1px solid var(--menu-sep-color);\n border-bottom: 1px solid var(--menu-sep-color);\n position: relative;\n text-align: left;\n color: var(--color);\n background-color: var(--menu-bg-color);\n min-height: var(--line-height);\n padding-top: 0.2em;\n padding-bottom: 0.2em;\n cursor: pointer;\n border-radius: var(--border-radius);\n}\n.muigui-root>div:nth-child(2),\n.muigui-menu>div:nth-child(2) {\n flex: 1 1 auto;\n}\n\n.muigui-controller {\n margin-left: 0.2em;\n margin-right: 0.2em;\n}\n.muigui-root.muigui-controller,\n.muigui-menu.muigui-controller {\n margin-left: 0;\n margin-right: 0;\n}\n.muigui-controller>*:nth-child(1) {\n flex: 1 0 var(--label-width);\n min-width: 0;\n white-space: pre;\n}\n.muigui-controller>label:nth-child(1) {\n place-content: center start;\n display: inline-grid;\n overflow: hidden;\n}\n.muigui-controller>*:nth-child(2) {\n flex: 1 1 75%;\n min-width: 0;\n}\n\n/* -----------------------------------------\n a label controller is [[label][value]]\n*/\n\n.muigui-label-controller {\n display: flex;\n margin: 0.4em 0 0.4em 0;\n word-wrap: initial;\n align-items: stretch;\n}\n\n.muigui-value {\n display: flex;\n align-items: stretch;\n}\n.muigui-value>* {\n flex: 1 1 auto;\n min-width: 0;\n}\n.muigui-value>*:nth-child(1) {\n flex: 1 1 calc(100% - var(--number-width));\n}\n.muigui-value>*:nth-child(2) {\n flex: 1 1 var(--number-width);\n margin-left: 0.2em;\n}\n\n/* fix! */\n.muigui-open>button>label::before,\n.muigui-closed>button>label::before {\n width: 1.25em;\n height: var(--line-height);\n display: inline-grid;\n place-content: center start;\n pointer-events: none;\n}\n.muigui-open>button>label::before {\n content: "โง"; /*"โผ";*/\n}\n.muigui-closed>button>label::before {\n content: "โจ"; /*"โถ";*/\n}\n.muigui-open>*:nth-child(2) {\n transition: max-height 0.2s ease-out,\n opacity 0.5s ease-out;\n max-height: 100vh;\n overflow: auto;\n opacity: 1;\n}\n\n.muigui-closed>*:nth-child(2) {\n transition: max-height 0.2s ease-out,\n opacity 1s;\n max-height: 0;\n opacity: 0;\n overflow: hidden;\n}\n\n/* ---- popdown ---- */\n\n.muigui-pop-down-top {\n display: flex;\n}\n/* fix? */\n.muigui-value>*:nth-child(1).muigui-pop-down-top {\n flex: 0;\n}\n.muigui-pop-down-bottom {\n\n}\n\n.muigui-pop-down-values {\n min-width: 0;\n display: flex;\n}\n.muigui-pop-down-values>* {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.muigui-value.muigui-pop-down-controller {\n flex-direction: column;\n}\n\n.muigui-pop-down-top input[type=checkbox] {\n -webkit-appearance: none;\n appearance: none;\n width: auto;\n color: var(--value-color);\n background-color: var(--value-bg-color);\n cursor: pointer;\n\n display: grid;\n place-content: center;\n margin: 0;\n font: inherit;\n color: currentColor;\n width: 1.7em;\n height: 1.7em;\n transform: translateY(-0.075em);\n}\n\n.muigui-pop-down-top input[type=checkbox]::before {\n content: "+";\n display: grid;\n place-content: center;\n border-radius: calc(var(--border-radius) + 2px);\n border-left: 1px solid rgba(255,255,255,0.3);\n border-top: 1px solid rgba(255,255,255,0.3);\n border-bottom: 1px solid rgba(0,0,0,0.2);\n border-right: 1px solid rgba(0,0,0,0.2);\n background-color: var(--range-color);\n color: var(--value-bg-color);\n width: calc(var(--line-height) - 4px);\n height: calc(var(--line-height) - 4px);\n}\n\n.muigui-pop-down-top input[type=checkbox]:checked::before {\n content: "๏ผธ";\n}\n\n\n/* ---- select ---- */\n\n.muigui select,\n.muigui option,\n.muigui input,\n.muigui button {\n color: var(--value-color);\n background-color: var(--value-bg-color);\n font-family: var(--font-family);\n font-size: var(--font-size);\n border: none;\n margin: 0;\n border-radius: var(--border-radius);\n}\n.muigui select {\n appearance: none;\n margin: 0;\n margin-left: 0; /*?*/\n overflow: hidden; /* Safari */\n}\n\n.muigui select:focus,\n.muigui input:focus,\n.muigui button:focus {\n outline: 1px solid var(--focus-color);\n}\n\n.muigui select:hover,\n.muigui option:hover,\n.muigui input:hover,\n.muigui button:hover {\n background-color: var(--hover-bg-color); \n}\n\n/* ------ [ label ] ------ */\n\n.muigui-label {\n border-top: 1px solid var(--menu-sep-color);\n border-bottom: 1px solid var(--menu-sep-color);\n padding-top: 0.4em;\n padding-bottom: 0.3em;\n place-content: center start;\n background-color: var(--menu-bg-color);\n white-space: pre;\n border-radius: var(--border-radius);\n}\n\n/* ------ [ divider] ------ */\n\n.muigui-divider {\n min-height: 6px;\n border-top: 2px solid var(--menu-sep-color);\n margin-top: 6px;\n}\n\n/* ------ [ button ] ------ */\n\n.muigui-button {\n display: grid;\n\n}\n.muigui-button button {\n border: none;\n color: var(--value-color);\n background-color: var(--button-bg-color);\n cursor: pointer;\n place-content: center center;\n}\n\n/* ------ [ color ] ------ */\n\n.muigui-color>div {\n overflow: hidden;\n position: relative;\n margin-left: 0;\n margin-right: 0; /* why? */\n max-width: var(--line-height);\n border-radius: var(--border-radius);\n}\n\n.muigui-color>div:focus-within {\n outline: 1px solid var(--focus-color);\n}\n\n.muigui-color input[type=color] {\n border: none;\n padding: 0;\n background: inherit;\n cursor: pointer;\n position: absolute;\n width: 200%;\n left: -10px;\n top: -10px;\n height: 200%;\n}\n.muigui-disabled canvas,\n.muigui-disabled svg,\n.muigui-disabled img,\n.muigui-disabled .muigui-color input[type=color] {\n opacity: 0.2;\n}\n\n/* ------ [ checkbox ] ------ */\n\n.muigui-checkbox>label:nth-child(2) {\n display: grid;\n place-content: center start;\n margin: 0;\n}\n\n.muigui-checkbox input[type=checkbox] {\n -webkit-appearance: none;\n appearance: none;\n width: auto;\n color: var(--value-color);\n background-color: var(--value-bg-color);\n cursor: pointer;\n\n display: grid;\n place-content: center;\n margin: 0;\n font: inherit;\n color: currentColor;\n width: 1.7em;\n height: 1.7em;\n transform: translateY(-0.075em);\n}\n\n.muigui-checkbox input[type=checkbox]::before {\n content: "";\n color: var(--value-color);\n display: grid;\n place-content: center;\n}\n\n.muigui-checkbox input[type=checkbox]:checked::before {\n content: "โ";\n}\n\n.muigui input[type=number]::-webkit-inner-spin-button, \n.muigui input[type=number]::-webkit-outer-spin-button { \n -webkit-appearance: none;\n appearance: none;\n margin: 0; \n}\n.muigui input[type=number] {\n -moz-appearance: textfield;\n}\n\n/* ------ [ radio grid ] ------ */\n\n.muigui-radio-grid>div {\n display: grid;\n gap: 2px;\n}\n\n.muigui-radio-grid input {\n appearance: none;\n display: none;\n}\n\n.muigui-radio-grid button {\n color: var(--color);\n width: 100%;\n text-align: left;\n}\n\n.muigui-radio-grid input:checked + button {\n color: var(--value-color);\n background-color: var(--selected-color);\n}\n\n/* ------ [ color-chooser ] ------ */\n\n.muigui-color-chooser-cursor {\n stroke-width: 1px;\n stroke: white;\n fill: none;\n}\n.muigui-color-chooser-circle {\n stroke-width: 1px;\n stroke: white;\n fill: none;\n}\n\n\n/* ------ [ vec2 ] ------ */\n\n.muigui-vec2 svg {\n background-color: var(--value-bg-color);\n}\n\n.muigui-vec2-axis {\n stroke: 1px;\n stroke: var(--focus-color);\n}\n\n.muigui-vec2-line {\n stroke-width: 1px;\n stroke: var(--value-color);\n fill: var(--value-color);\n}\n\n/* ------ [ direction ] ------ */\n\n.muigui-direction svg {\n background-color: rgba(0,0,0,0.2);\n}\n\n.muigui-direction:focus-within svg {\n outline: none;\n}\n.muigui-direction-range {\n fill: var(--value-bg-color);\n}\n.muigui-direction svg:focus {\n outline: none;\n}\n.muigui-direction svg:focus .muigui-direction-range {\n stroke-width: 0.5px;\n stroke: var(--focus-color);\n}\n\n.muigui-direction-arrow {\n fill: var(--value-color);\n}\n\n/* ------ [ slider ] ------ */\n\n.muigui-slider>div {\n display: flex;\n align-items: stretch;\n height: var(--line-height);\n}\n.muigui-slider svg {\n flex: 1 1 auto;\n}\n.muigui-slider .muigui-slider-up #muigui-orientation {\n transform: scale(1, -1) translateY(-100%);\n}\n\n.muigui-slider .muigui-slider-up #muigui-number-orientation {\n transform: scale(1,-1);\n}\n\n.muigui-ticks {\n stroke: var(--range-color);\n}\n.muigui-thicks {\n stroke: var(--color);\n stroke-width: 2px;\n}\n.muigui-svg-text {\n fill: var(--color);\n font-size: 7px;\n}\n.muigui-mark {\n fill: var(--value-color);\n}\n\n/* ------ [ range ] ------ */\n\n\n.muigui-range input[type=range] {\n -webkit-appearance: none;\n appearance: none;\n background-color: transparent;\n}\n\n.muigui-range input[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n border-radius: calc(var(--border-radius) + 2px);\n border-left: 1px solid rgba(255,255,255,0.3);\n border-top: 1px solid rgba(255,255,255,0.3);\n border-bottom: 1px solid rgba(0,0,0,0.2);\n border-right: 1px solid rgba(0,0,0,0.2);\n background-color: var(--range-color);\n margin-top: calc((var(--line-height) - 2px) / -2);\n width: calc(var(--line-height) - 2px);\n height: calc(var(--line-height) - 2px);\n}\n\n.muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: none;\n appearance: none;\n border: 1px solid var(--menu-sep-color);\n height: 2px;\n}\n\n\n/* dat.gui style - doesn\'t work on Safari iOS */\n\n/*\n.muigui-range input[type=range] {\n cursor: ew-resize;\n overflow: hidden;\n}\n\n.muigui-range input[type=range] {\n -webkit-appearance: none;\n appearance: none;\n background-color: var(--range-right-color);\n margin: 0;\n}\n.muigui-range input[type=range]:hover {\n background-color: var(--range-right-hover-color);\n}\n\n.muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: none;\n appearance: none;\n height: max-content;\n color: var(--range-left-color);\n margin-top: -1px;\n}\n\n.muigui-range input[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 0px;\n height: max-content;\n box-shadow: -1000px 0 0 1000px var(--range-left-color);\n}\n*/\n\n/* FF */\n/*\n.muigui-range input[type=range]::-moz-slider-progress {\n background-color: var(--range-left-color); \n}\n.muigui-range input[type=range]::-moz-slider-thumb {\n height: max-content;\n width: 0;\n border: none;\n box-shadow: -1000px 0 0 1000px var(--range-left-color);\n box-sizing: border-box;\n}\n*/\n\n.muigui-checkered-background {\n background-color: #404040;\n background-image:\n linear-gradient(45deg, #808080 25%, transparent 25%),\n linear-gradient(-45deg, #808080 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #808080 75%),\n linear-gradient(-45deg, transparent 75%, #808080 75%);\n background-size: 16px 16px;\n background-position: 0 0, 0 8px, 8px -8px, -8px 0px;\n}\n\n/* ---------------------------------------------------------- */\n\n/* needs to be at bottom to take precedence */\n.muigui-auto-place {\n max-height: 100%;\n position: fixed;\n top: 0;\n right: 15px;\n z-index: 100001;\n}\n\n',themes:{default:"",float:"\n :root {\n color-scheme: light dark,\n }\n\n .muigui {\n --width: 400px;\n --bg-color: initial;\n --label-width: 25%;\n --number-width: 20%;\n }\n\n input,\n .muigui-label-controller>label {\n text-shadow:\n -1px -1px 0 var(--contrast-color),\n 1px -1px 0 var(--contrast-color),\n -1px 1px 0 var(--contrast-color),\n 1px 1px 0 var(--contrast-color);\n }\n\n .muigui-controller > label:nth-child(1) {\n place-content: center end;\n margin-right: 1em;\n }\n\n .muigui-value > :nth-child(2) {\n margin-left: 1em;\n }\n\n .muigui-root>*:nth-child(1) {\n display: none;\n }\n\n .muigui-range input[type=range]::-webkit-slider-thumb {\n border-radius: 1em;\n }\n\n .muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: initial;\n appearance: none;\n border: 1px solid rgba(0, 0, 0, 0.25);\n height: 2px;\n }\n\n .muigui-colors {\n --value-color: var(--color );\n --value-bg-color: rgba(0, 0, 0, 0.1);\n --disabled-color: #cccccc;\n --menu-bg-color: rgba(0, 0, 0, 0.1);\n --menu-sep-color: #bbbbbb;\n --hover-bg-color: rgba(0, 0, 0, 0);\n --invalid-color: #FF0000;\n --selected-color: rgba(0, 0, 0, 0.3);\n --range-color: rgba(0, 0, 0, 0.125);\n }\n"}};function e(t,e={},n=[]){const i=document.createElement(t);return function(t,e,n){for(const[n,i]of Object.entries(e))if("function"==typeof i&&n.startsWith("on")){const e=n.substring(2).toLowerCase();t.addEventListener(e,i,{passive:!1})}else if("object"==typeof i)for(const[e,o]of Object.entries(i))t[n][e]=o;else void 0===t[n]?t.setAttribute(n,i):t[n]=i;for(const e of n)t.appendChild(e)}(i,e,n),i}let n=0;function i(t,e){const n=t.indexOf(e);return n&&t.splice(n,1),t}function o(t,e,n){return Math.max(e,Math.min(n,t))}const r="undefined"!=typeof SharedArrayBuffer?function(t){return t&&t.buffer&&(t.buffer instanceof ArrayBuffer||t.buffer instanceof SharedArrayBuffer)}:function(t){return t&&t.buffer&&t.buffer instanceof ArrayBuffer},s=(t,e,n)=>Math.round(e(t)/n)/(1/n),a=(t,e)=>(t%e+e)%e;function l(t,e){for(const n in e)n in t&&(t[n]=e[n]);return t}const u=(t,e,n,i,o)=>(t-e)*(o-i)/(n-e)+i,c=({from:t,to:e})=>({to:n=>u(n,...t,...e),from:n=>[!0,u(n,...e,...t)]}),h=({from:t,to:e,step:n})=>({min:e[0],max:e[1],...n&&{step:n},converters:c({from:t,to:e})}),d={to:t=>t,from:t=>[!0,t]};function p(t,e,n,i,o){const{converters:{from:r}=d}=o,{min:s,max:a}=o,l=o.minRange||0,u=r(l)[1],c=t.add(e,n,{...o,min:s,max:a-l}).onChange((t=>{h.setValue(Math.min(a,Math.max(t+u,e[i])))})),h=t.add(e,i,{...o,min:s+l,max:a}).onChange((t=>{c.setValue(Math.max(s,Math.min(t-u,e[n])))}));return[c,h]}class m{domElement;#t;#e=[];constructor(t){this.domElement=t,this.#t=t}addElem(t){return this.#t.appendChild(t),t}removeElem(t){return this.#t.removeChild(t),t}pushSubElem(t){this.#t.appendChild(t),this.#t=t}popSubElem(){this.#t=this.#t.parentElement}add(t){return this.#e.push(t),this.addElem(t.domElement),t}remove(t){return this.removeElem(t.domElement),i(this.#e,t),t}pushSubView(t){this.pushSubElem(t.domElement)}popSubView(){this.popSubElem()}setOptions(t){for(const e of this.#e)e.setOptions(t)}updateDisplayIfNeeded(t,e){for(const n of this.#e)n.updateDisplayIfNeeded(t,e);return this}$(t){return this.domElement.querySelector(t)}}class g extends m{#n;#i;#o;constructor(t){super(e("div",{className:"muigui-controller"})),this.#n=[],this.#i=[],t&&this.domElement.classList.add(t)}get parent(){return this.#o}setParent(t){this.#o=t,this.enable(!this.disabled())}show(t=!0){return this.domElement.classList.toggle("muigui-hide",!t),this.domElement.classList.toggle("muigui-show",t),this}hide(){return this.show(!1)}disabled(){return!!this.domElement.closest(".muigui-disabled")}enable(t=!0){return this.domElement.classList.toggle("muigui-disabled",!t),["input","button","select","textarea"].forEach((t=>{this.domElement.querySelectorAll(t).forEach((t=>{const e=!!t.closest(".muigui-disabled");t.disabled=e}))})),this}disable(t=!0){return this.enable(!t)}onChange(t){return this.removeChange(t),this.#n.push(t),this}removeChange(t){return i(this.#n,t),this}onFinishChange(t){return this.removeFinishChange(t),this.#i.push(t),this}removeFinishChange(t){return i(this.#i,t),this}#r(t,e){for(const n of t)n.call(this,e)}emitChange(t,e,n){this.#r(this.#n,t),this.#o&&(void 0===e?this.#o.emitChange(t):this.#o.emitChange({object:e,property:n,value:t,controller:this}))}emitFinalChange(t,e,n){this.#r(this.#i,t),this.#o&&(void 0===e?this.#o.emitChange(t):this.#o.emitFinalChange({object:e,property:n,value:t,controller:this}))}updateDisplay(){}getColors(){const t=t=>t.replace(/-([a-z])/g,((t,e)=>e.toUpperCase())),n=e("div");this.domElement.appendChild(n);const i=Object.fromEntries(["color","bg-color","value-color","value-bg-color","hover-bg-color","menu-bg-color","menu-sep-color","disabled-color"].map((e=>{n.style.color=`var(--${e})`;const i=getComputedStyle(n);return[t(e),i.color]})));return n.remove(),i}}class f extends g{#s;#a;#l;#u={name:""};constructor(t,n,i={}){super("muigui-button",""),this.#s=t,this.#a=n,this.#l=this.addElem(e("button",{type:"button",onClick:()=>{this.#s[this.#a](this)}})),this.setOptions({name:n,...i})}setOptions(t){l(this.#u,t);const{name:e}=this.#u;this.#l.textContent=e}}function b(t,e){if(t.length!==e.length)return!1;for(let n=0;n{t.setValue(i.checked)},onChange:()=>{t.setFinalValue(i.checked)}});super(e("label",{},[i])),this.#b=i}updateDisplay(t){this.#b.checked=t}}const w=[],y=new Set;let k,E;function $(){k=void 0,E=!0;for(const t of w)y.has(t)||t();E=!1,y.size&&(E?C():(y.forEach((t=>{i(w,t)})),y.clear())),C()}function C(){!k&&w.length&&(k=requestAnimationFrame($))}let V=0;function I(){return"muigui-"+ ++V}class M extends m{constructor(t=""){super(e("div",{className:"muigui-value"})),t&&this.domElement.classList.add(t)}}class S extends g{#v;#x;constructor(t="",n=""){super("muigui-label-controller"),this.#v=I(),this.#x=e("label",{for:this.#v}),this.domElement.appendChild(this.#x),this.pushSubView(new M(t)),this.name(n)}get id(){return this.#v}name(t){return this.#x.title===this.#x.textContent&&(this.#x.title=t),this.#x.textContent=t,this}tooltip(t){this.#x.title=t}}class D extends S{#s;#a;#w;#y;#e;#k;constructor(t,e,n=""){super(n,e),this.#s=t,this.#a=e,this.#w=this.getValue(),this.#y=!1,this.#e=[]}get initialValue(){return this.#w}get object(){return this.#s}get property(){return this.#a}add(t){return this.#e.push(t),super.add(t),this.updateDisplay(),t}#E(t,e){let n=!1;if("object"==typeof t){const e=this.#s[this.#a];if(Array.isArray(t)||r(t))for(let i=0;i=0&&w.splice(e,1)}(this.#k)),this}}class N extends D{constructor(t,e){super(t,e,"muigui-checkbox");const n=this.id;this.add(new x(this,n)),this.updateDisplay()}}const F={to:t=>t,from:t=>[!0,t]},A={to:t=>t.toString(),from:t=>{const e=parseFloat(t);return[!Number.isNaN(e),e]}},U={radToDeg:c({to:[0,180],from:[0,Math.PI]})};function L(){let t=0;return function(e,n,i=5){t-=e.deltaY*n/i;const o=Math.floor(Math.abs(t)/n)*Math.sign(t)*n;return t-=o,o}}class O extends v{#$;#C;#V;#I;#u={step:.01,converters:A,min:Number.NEGATIVE_INFINITY,max:Number.POSITIVE_INFINITY};constructor(t,n){const i=t.setValue.bind(t),r=t.setFinalValue.bind(t),a=L();super(e("input",{type:"number",onInput:()=>this.#M(i,!0),onChange:()=>this.#M(r,!1),onWheel:e=>{e.preventDefault();const{min:n,max:i,step:r}=this.#u,l=a(e,r),u=parseFloat(this.domElement.value),c=o(s(u+l,(t=>t),r),n,i);t.setValue(c)}})),this.setOptions(n)}#M(t,e){const n=parseFloat(this.domElement.value),[i,r]=this.#C(n);let s;if(i&&!Number.isNaN(n)){const{min:n,max:i}=this.#u;s=r>=n&&r<=i,this.#I=e,t(o(r,n,i))}this.domElement.classList.toggle("muigui-invalid-value",!i||!s)}updateDisplay(t){this.#I||(this.domElement.value=s(t,this.#$,this.#V)),this.#I=!1}setOptions(t){l(this.#u,t);const{step:e,converters:{to:n,from:i}}=this.#u;return this.#$=n,this.#C=i,this.#V=e,this}}class j extends D{#S;#V;constructor(t,e,n={}){super(t,e,"muigui-checkbox"),this.#S=this.add(new O(this,n)),this.updateDisplay()}}class T extends v{#D;constructor(t,n){const i=[];super(e("select",{onChange:()=>{t.setFinalValue(this.#D[this.domElement.selectedIndex])}},n.map((([t,n])=>(i.push(n),e("option",{textContent:t})))))),this.#D=i}updateDisplay(t){const e=this.#D.indexOf(t);this.domElement.selectedIndex=e}}function H(t,e){return Array.isArray(t)?Array.isArray(t[0])?t:e?t.map(((t,e)=>[t,e])):t.map((t=>[t,t])):[...Object.entries(t)]}class z extends D{constructor(t,e,n){super(t,e,"muigui-select");const i="number"==typeof this.getValue(),{keyValues:o}=n,r=H(o,i);this.add(new T(this,r)),this.updateDisplay()}}class P extends v{#$;#C;#V;#I;#u={step:.01,min:0,max:1,converters:F};constructor(t,n){const i=L();super(e("input",{type:"range",onInput:()=>{this.#I=!0;const{min:e,max:n,step:i}=this.#u,r=parseFloat(this.domElement.value),a=o(s(r,(t=>t),i),e,n),[l,u]=this.#C(a);l&&t.setValue(u)},onChange:()=>{this.#I=!0;const{min:e,max:n,step:i}=this.#u,r=parseFloat(this.domElement.value),a=o(s(r,(t=>t),i),e,n),[l,u]=this.#C(a);l&&t.setFinalValue(u)},onWheel:e=>{e.preventDefault();const[n,r]=this.#C(parseFloat(this.domElement.value));if(!n)return;const{min:a,max:l,step:u}=this.#u,c=i(e,u),h=o(s(r+c,(t=>t),u),a,l);t.setValue(h)}})),this.setOptions(n)}updateDisplay(t){this.#I||(this.domElement.value=s(t,this.#$,this.#V)),this.#I=!1}setOptions(t){l(this.#u,t);const{step:e,min:n,max:i,converters:{to:o,from:r}}=this.#u;return this.#$=o,this.#C=r,this.#V=e,this.domElement.step=e,this.domElement.min=n,this.domElement.max=i,this}}class B extends D{constructor(t,e,n){super(t,e,"muigui-range"),this.add(new P(this,n)),this.add(new O(this,n))}}class G extends v{#$;#C;#I;#u={converters:F};constructor(t,n){const i=t.setValue.bind(t),o=t.setFinalValue.bind(t);super(e("input",{type:"text",onInput:()=>this.#M(i,!0),onChange:()=>this.#M(o,!1)})),this.setOptions(n)}#M(t,e){const[n,i]=this.#C(this.domElement.value);n&&(this.#I=e,t(i)),this.domElement.style.color=n?"":"var(--invalid-color)"}updateDisplay(t){this.#I||(this.domElement.value=this.#$(t),this.domElement.style.color=""),this.#I=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n}}=this.#u;return this.#$=e,this.#C=n,this}}class R extends D{constructor(t,e){super(t,e,"muigui-checkbox"),this.add(new G(this)),this.updateDisplay()}}const _=(t,e,n)=>Math.max(e,Math.min(n,t)),Y=(t,e,n)=>t+(e-t)*n,W=t=>t>=0?t%1:1-t%1,q=t=>+t.toFixed(0),K=t=>+t.toFixed(3),J=t=>parseInt(t.substring(1,3),16)<<16|parseInt(t.substring(3,5),16)<<8|parseInt(t.substring(5,7),16),X=t=>parseInt(t.substring(1,3),16)*2**24+65536*parseInt(t.substring(3,5),16)+256*parseInt(t.substring(5,7),16)+parseInt(t.substring(7,9),16),Z=t=>[parseInt(t.substring(1,3),16),parseInt(t.substring(3,5),16),parseInt(t.substring(5,7),16)],Q=t=>`#${Array.from(t).map((t=>t.toString(16).padStart(2,"0"))).join("")}`,tt=t=>[parseInt(t.substring(1,3),16),parseInt(t.substring(3,5),16),parseInt(t.substring(5,7),16),parseInt(t.substring(7,9),16)],et=t=>`#${Array.from(t).map((t=>t.toString(16).padStart(2,"0"))).join("")}`,nt=t=>Z(t).map((t=>K(t/255))),it=t=>Q(Array.from(t).map((t=>Math.round(_(255*t,0,255))))),ot=t=>tt(t).map((t=>K(t/255))),rt=t=>et(Array.from(t).map((t=>Math.round(_(255*t,0,255))))),st=t=>_(Math.round(255*t),0,255).toString(16).padStart(2,"0"),at=t=>({r:parseInt(t.substring(1,3),16)/255,g:parseInt(t.substring(3,5),16)/255,b:parseInt(t.substring(5,7),16)/255}),lt=t=>({r:parseInt(t.substring(1,3),16)/255,g:parseInt(t.substring(3,5),16)/255,b:parseInt(t.substring(5,7),16)/255,a:parseInt(t.substring(7,9),16)/255}),ut=t=>`rgb(${Z(t).join(", ")})`,ct=/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,ht=t=>`rgba(${tt(t).map(((t,e)=>3===e?t/255:t)).join(", ")})`,dt=/^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+\.\d+|\d+)\s*\)\s*$/,pt=t=>{const e=yt(Z(t)).map((t=>q(t)));return`hsl(${e[0]}, ${e[1]}%, ${e[2]}%)`},mt=t=>{const e=kt(tt(t)).map(((t,e)=>3===e?K(t):q(t)));return`hsl(${e[0]} ${e[1]}% ${e[2]}% / ${e[3]})`},gt=/^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\)\s*$/,ft=/^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\/\s*(\d+\.\d+|\d+)\s*\)\s*$/,bt=(t,e)=>(t%e+e)%e;function vt([t,e,n]){t=bt(t,360),e=_(e/100,0,1),n=_(n/100,0,1);const i=e*Math.min(n,1-n);function o(e){const o=(e+t/30)%12;return n-i*Math.max(-1,Math.min(o-3,9-o,1))}return[o(0),o(8),o(4)].map((t=>Math.round(255*t)))}function xt([t,e,n]){const i=Math.max(t,e,n),o=Math.min(t,e,n),r=.5*(o+i),s=i-o;let a=0,l=0;if(0!==s)switch(l=0===r||1===r?0:(i-r)/Math.min(r,1-r),i){case t:a=(e-n)/s+(e{const[e,n,i]=xt(t.map((t=>t/255)));return[360*e,100*n,100*i]},kt=t=>{const[e,n,i,o]=wt(t.map((t=>t/255)));return[360*e,100*n,100*i,o]};function Et([t,e,n]){return e=_(e,0,1),n=_(n,0,1),[t,t+2/3,t+1/3].map((t=>Y(1,_(Math.abs(6*W(t)-3)-1,0,1),e)*n))}function $t([t,e,n,i]){return[...Et([t,e,n]),i]}const Ct=t=>Math.round(1e3*t)/1e3;function Vt([t,e,n]){const i=n>e?[n,e,-1,2/3]:[e,n,0,-1/3],o=i[0]>t?[i[0],i[1],i[3],t]:[t,i[1],i[2],i[0]],r=o[0]-Math.min(o[3],o[1]);return[Math.abs(o[2]+(o[3]-o[1])/(6*r+Number.EPSILON)),r/(o[0]+Number.EPSILON),o[0]].map(Ct)}const It=t=>t.endsWith("a")||t.startsWith("hex8"),Mt=[{re:/^#(?:[0-9a-f]){6}$/i,format:"hex6"},{re:/^(?:[0-9a-f]){6}$/i,format:"hex6-no-hash"},{re:/^#(?:[0-9a-f]){8}$/i,format:"hex8"},{re:/^(?:[0-9a-f]){8}$/i,format:"hex8-no-hash"},{re:/^#(?:[0-9a-f]){3}$/i,format:"hex3"},{re:/^(?:[0-9a-f]){3}$/i,format:"hex3-no-hash"},{re:ct,format:"css-rgb"},{re:gt,format:"css-hsl"},{re:dt,format:"css-rgba"},{re:ft,format:"css-hsla"}];function St(t){switch(typeof t){case"number":return console.warn('can not reliably guess format based on a number. You should pass in a format like {format: "uint32-rgb"} or {format: "uint32-rgb"}'),t<=16777215?"uint32-rgb":"uint32-rgba";case"string":{const e=function(t){for(const e of Mt)if(e.re.test(t))return e}(t.trim());if(e)return e.format;break}case"object":if(t instanceof Uint8Array||t instanceof Uint8ClampedArray){if(3===t.length)return"uint8-rgb";if(4===t.length)return"uint8-rgba"}else if(t instanceof Float32Array){if(3===t.length)return"float-rgb";if(4===t.length)return"float-rgba"}else if(Array.isArray(t)){if(3===t.length)return"float-rgb";if(4===t.length)return"float-rgba"}else if("r"in t&&"g"in t&&"b"in t)return"a"in t?"object-rgba":"object-rgb"}throw new Error(`unknown color format: ${t}`)}function Dt(t){return t.trim(t)}function Nt(t){return t.trim(t)}function Ft(t){return t[1]===t[2]&&t[3]===t[4]&&t[5]===t[6]?`#${t[1]}${t[3]}${t[5]}`:t}const At=/^(#|)([0-9a-f]{3})$/i;function Ut(t){const e=At.exec(t);if(e){const[,,t]=e;return"#"+`${(n=t)[0]}${n[0]}${n[1]}${n[1]}${n[2]}${n[2]}`}var n;return t}function Lt(t){return Ft(Dt(t))}const Ot=t=>{const e=ct.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),`rgb(${n.join(", ")})`]},jt=t=>{const e=dt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map(((t,e)=>3===e?parseFloat(t):parseInt(t)));return[!n.find((t=>t>255)),`rgba(${n.join(", ")})`]},Tt=t=>{const e=gt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseFloat(t)));return[!n.find((t=>Number.isNaN(t))),`hsl(${n[0]}, ${n[1]}%, ${n[2]}%)`]},Ht=t=>{const e=ft.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map((t=>parseFloat(t)));return[!n.find((t=>Number.isNaN(t))),`hsl(${n[0]} ${n[1]}% ${n[2]}% / ${n[3]})`]},zt=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/,Pt=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/,Bt=/^\s*(?:0x){0,1}([0-9a-z]{1,6})\s*$/i,Gt=/^\s*(?:0x){0,1}([0-9a-z]{1,8})\s*$/i,Rt=/^\s*#[a-f0-9]{6}\s*$|^\s*#[a-f0-9]{3}\s*$/i,_t=/^\s*[a-f0-9]{6}\s*$/i,Yt=/^\s*#[a-f0-9]{8}\s*$/i,Wt=/^\s*[a-f0-9]{8}\s*$/i,qt={hex6:{color:{from:t=>[!0,t],to:Dt},text:{from:t=>[Rt.test(t),t.trim()],to:t=>t}},hex8:{color:{from:t=>[!0,t],to:Nt},text:{from:t=>[Yt.test(t),t.trim()],to:t=>t}},hex3:{color:{from:t=>[!0,Lt(t)],to:Ut},text:{from:t=>[Rt.test(t),Ft(t.trim())],to:t=>t}},"hex6-no-hash":{color:{from:t=>[!0,t.substring(1)],to:t=>`#${Dt(t)}`},text:{from:t=>[_t.test(t),t.trim()],to:t=>t}},"hex8-no-hash":{color:{from:t=>[!0,t.substring(1)],to:t=>`#${Nt(t)}`},text:{from:t=>[Wt.test(t),t.trim()],to:t=>t}},"hex3-no-hash":{color:{from:t=>[!0,Lt(t).substring(1)],to:Ut},text:{from:t=>[_t.test(t),Ft(t.trim())],to:t=>t}},"uint32-rgb":{color:{from:t=>[!0,J(t)],to:t=>`#${Math.round(t).toString(16).padStart(6,"0")}`},text:{from:t=>(t=>{const e=Bt.exec(t);return e?[!0,parseInt(e[1],16)]:[!1]})(t),to:t=>`0x${t.toString(16).padStart(6,"0")}`}},"uint32-rgba":{color:{from:t=>[!0,X(t)],to:t=>`#${Math.round(t).toString(16).padStart(8,"0")}`},text:{from:t=>(t=>{const e=Gt.exec(t);return e?[!0,parseInt(e[1],16)]:[!1]})(t),to:t=>`0x${t.toString(16).padStart(8,"0")}`}},"uint8-rgb":{color:{from:t=>[!0,Z(t)],to:Q},text:{from:t=>{const e=zt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),n]},to:t=>t.join(", ")}},"uint8-rgba":{color:{from:t=>[!0,tt(t)],to:et},text:{from:t=>{const e=Pt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),n]},to:t=>t.join(", ")}},"float-rgb":{color:{from:t=>[!0,nt(t)],to:it},text:{from:t=>{const e=t.split(",").map((t=>t.trim())),n=e.map((t=>parseFloat(t)));if(3!==n.length)return[!1];const i=e.findIndex((t=>isNaN(t)));return[i<0,n.map((t=>K(t)))]},to:t=>Array.from(t).map((t=>K(t))).join(", ")}},"float-rgba":{color:{from:t=>[!0,ot(t)],to:rt},text:{from:t=>{const e=t.split(",").map((t=>t.trim())),n=e.map((t=>parseFloat(t)));if(4!==n.length)return[!1];const i=e.findIndex((t=>isNaN(t)));return[i<0,n.map((t=>K(t)))]},to:t=>Array.from(t).map((t=>K(t))).join(", ")}},"object-rgb":{color:{from:t=>[!0,at(t)],to:t=>`#${st(t.r)}${st(t.g)}${st(t.b)}`},text:{from:t=>{try{const e=t.replace(/([a-z])/g,'"$1"'),n=JSON.parse(e);if(Number.isNaN(n.r)||Number.isNaN(n.g)||Number.isNaN(n.b))throw new Error("not {r, g, b}");return[!0,n]}catch(t){return[!1]}},to:t=>`{r:${K(t.r)}, g:${K(t.g)}, b:${K(t.b)}}`}},"object-rgba":{color:{from:t=>[!0,lt(t)],to:t=>`#${st(t.r)}${st(t.g)}${st(t.b)}${st(t.a)}`},text:{from:t=>{try{const e=t.replace(/([a-z])/g,'"$1"'),n=JSON.parse(e);if(Number.isNaN(n.r)||Number.isNaN(n.g)||Number.isNaN(n.b)||Number.isNaN(n.a))throw new Error("not {r, g, b, a}");return[!0,n]}catch(t){return[!1]}},to:t=>`{r:${K(t.r)}, g:${K(t.g)}, b:${K(t.b)}}, a:${K(t.a)}}`}},"css-rgb":{color:{from:t=>[!0,ut(t)],to:t=>{const e=ct.exec(t);return Q([e[1],e[2],e[3]].map((t=>parseInt(t))))}},text:{from:Ot,to:t=>Ot(t)[1]}},"css-rgba":{color:{from:t=>[!0,ht(t)],to:t=>{const e=dt.exec(t);return et([e[1],e[2],e[3],e[4]].map(((t,e)=>3===e?255*parseFloat(t)|0:parseInt(t))))}},text:{from:jt,to:t=>jt(t)[1]}},"css-hsl":{color:{from:t=>[!0,pt(t)],to:t=>{const e=gt.exec(t),n=vt([e[1],e[2],e[3]].map((t=>parseFloat(t))));return Q(n)}},text:{from:Tt,to:t=>Tt(t)[1]}},"css-hsla":{color:{from:t=>[!0,mt(t)],to:t=>{const e=ft.exec(t),n=function([t,e,n,i]){return[...vt([t,e,n]),255*i|0]}([e[1],e[2],e[3],e[4]].map((t=>parseFloat(t))));return et(n)}},text:{from:Ht,to:t=>Ht(t)[1]}}};class Kt extends m{constructor(t,n){super(e(t,{className:n}))}}class Jt extends S{#N;constructor(){super("muigui-canvas"),this.#N=this.add(new Kt("canvas","muigui-canvas")).domElement}get canvas(){return this.#N}}class Xt extends v{#$;#C;#F;#I;#u={converters:F};constructor(t,n){const i=e("input",{type:"color",onInput:()=>{const[e,n]=this.#C(i.value);e&&(this.#I=!0,t.setValue(n))},onChange:()=>{const[e,n]=this.#C(i.value);e&&(this.#I=!0,t.setFinalValue(n))}});super(e("div",{},[i])),this.setOptions(n),this.#F=i}updateDisplay(t){this.#I||(this.#F.value=this.#$(t)),this.#I=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n}}=this.#u;return this.#$=e,this.#C=n,this}}class Zt extends D{#A;#S;constructor(t,e,n={}){super(t,e,"muigui-color");const i=n.format||St(this.getValue()),{color:o,text:r}=qt[i];this.#A=this.add(new Xt(this,{converters:o})),this.#S=this.add(new G(this,{converters:r})),this.updateDisplay()}setOptions(t){const{format:e}=t;if(e){const{color:t,text:n}=qt[e];this.#A.setOptions({converters:t}),this.#S.setOptions({converters:n})}return super.setOptions(t),this}}class Qt extends g{constructor(){super("muigui-divider")}}class te extends g{#U;#L;constructor(t){super(t),this.#U=[],this.#L=this}get children(){return this.#U}get controllers(){return this.#U.filter((t=>!(t instanceof te)))}get folders(){return this.#U.filter((t=>t instanceof te))}reset(t=!0){for(const e of this.#U)e instanceof te&&!t||e.reset(t);return this}updateDisplay(){for(const t of this.#U)t.updateDisplay();return this}remove(t){const e=this.#U.indexOf(t);if(e>=0){const t=this.#U.splice(e,1)[0];t.domElement.remove(),t.setParent(null)}return this}_addControllerImpl(t){return this.domElement.appendChild(t.domElement),this.#U.push(t),t.setParent(this),t}addController(t){return this.#L._addControllerImpl(t)}pushContainer(t){return this.addController(t),this.#L=t,t}popContainer(){return this.#L=this.#L.parent,this}}class ee extends te{#O;constructor(t="Controls",n="muigui-menu"){super(n),this.#O=e("label"),this.addElem(e("button",{type:"button",onClick:()=>this.toggleOpen()},[this.#O])),this.pushContainer(new te),this.name(t),this.open()}open(t=!0){return this.domElement.classList.toggle("muigui-closed",!t),this.domElement.classList.toggle("muigui-open",t),this}close(){return this.open(!1)}name(t){return this.#O.textContent=t,this}title(t){return this.name(t)}toggleOpen(){return this.open(!this.domElement.classList.contains("muigui-open")),this}}class ne extends g{constructor(t){super("muigui-label"),this.text(t)}text(t){return this.domElement.textContent=t,this}}function ie(){}function oe(t,e,n){const i=t.getBoundingClientRect(),o=e.clientX-i.left,r=e.clientY-i.top,s=o/i.width,a=r/i.height,l=o-(n=n||[o,r])[0],u=r-n[1];return{x:o,y:r,nx:s,ny:a,dx:l,dy:u,ndx:l/i.width,ndy:u/i.width}}function re(t,{onDown:e=ie,onMove:n=ie,onUp:i=ie}){let o;const r=function(e){const i={type:"move",...oe(t,e,o)};n(i)},s=function(e){t.releasePointerCapture(e.pointerId),t.removeEventListener("pointermove",r),t.removeEventListener("pointerup",s),document.body.style.backgroundColor="",i("up")},a=function(n){t.addEventListener("pointermove",r),t.addEventListener("pointerup",s),t.setPointerCapture(n.pointerId);const i=oe(t,n);o=[i.x,i.y],e({type:"down",...i})};return t.addEventListener("pointerdown",a),function(){t.removeEventListener("pointerdown",a)}}function se(t){return t.querySelectorAll("[data-src]").forEach((e=>{const i="muigui-id-"+n++;e.id=i,t.querySelectorAll(`[data-target=${e.dataset.src}]`).forEach((t=>{t.setAttribute("fill",`url(#${i})`)}))})),t}class ae extends v{#$;#C;#j;#T;#H;#z;#P;#B;#G;#R;#_;#Y;#W;#q;#u={converters:F,alpha:!1};#K;#J;constructor(t,n){super(e("div",{innerHTML:'\n\n\n\n',className:"muigui-no-scroll"})),this.#j=this.domElement.children[0],this.#H=this.domElement.children[1],this.#B=this.domElement.children[2],se(this.#j),se(this.#H),se(this.#B),this.#T=this.$(".muigui-color-chooser-circle"),this.#z=this.$("[data-src=muigui-color-chooser-hue]"),this.#P=this.$(".muigui-color-chooser-hue-cursor"),this.#G=this.$("[data-src=muigui-color-chooser-alpha]"),this.#R=this.$(".muigui-color-chooser-alpha-cursor");const i=e=>{const n=o(e.nx,0,1),i=o(e.ny,0,1);this.#_[1]=n,this.#_[2]=1-i,this.#Y=!0,this.#q=!0;const[r,s]=this.#C(this.#K(this.#_));r&&t.setValue(s)},r=e=>{const n=o(e.nx,0,1);this.#_[0]=n,this.#W=!0,this.#q=!0;const[i,r]=this.#C(this.#K(this.#_));i&&t.setValue(r)},s=e=>{const n=o(e.nx,0,1);this.#_[3]=n,this.#Y=!0,this.#W=!0;const[i,r]=this.#C(this.#K(this.#_));i&&t.setValue(r)};re(this.#j,{onDown:i,onMove:i}),re(this.#H,{onDown:r,onMove:r}),re(this.#B,{onDown:s,onMove:s}),this.setOptions(n)}updateDisplay(t){this.#_||(this.#_=this.#J(this.#$(t)));{const[e,n,i,o=1]=this.#J(this.#$(t));this.#Y||(this.#_[0]=n>.001&&i>.001?e:this.#_[0]),this.#W||(this.#_[1]=n,this.#_[2]=i),this.#q||(this.#_[3]=o)}{const[t,e,n,i]=this.#_,[o,r,s]=wt($t(this.#_));this.#Y||this.#P.setAttribute("transform",`translate(${64*t}, 0)`),this.#z.children[0].setAttribute("stop-color",`hsl(${360*o} 0% 100% / ${i})`),this.#z.children[1].setAttribute("stop-color",`hsl(${360*o} 100% 50% / ${i})`),this.#q||this.#R.setAttribute("transform",`translate(${64*i}, 0)`),this.#G.children[0].setAttribute("stop-color",`hsl(${360*o} ${100*r}% ${100*s}% / 0)`),this.#G.children[1].setAttribute("stop-color",`hsl(${360*o} ${100*r}% ${100*s}% / 1)`),this.#W||(this.#T.setAttribute("cx",""+64*e),this.#T.setAttribute("cy",""+48*(1-n)))}this.#Y=!1,this.#W=!1,this.#q=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n},alpha:i}=this.#u;return this.#B.style.display=i?"":"none",this.#K=i?t=>rt($t(t)):t=>it(Et(t)),this.#J=i?t=>function([t,e,n,i]){return[...Vt([t,e,n]),i]}(ot(t)):t=>Vt(nt(t)),this.#$=e,this.#C=n,this}}class le extends D{#X;#Z;#b;#Q;#u={open:!1};constructor(t,n,i={}){super(t,n,"muigui-pop-down-controller"),this.#X=this.add(new Kt("div","muigui-pop-down-top"));const o=this.#X.addElem(e("input",{type:"checkbox",onChange:()=>{this.#u.open=o.checked,this.updateDisplay()}}));this.#b=o,this.#Z=this.#X.add(new Kt("div","muigui-pop-down-values")),this.#Q=this.add(new Kt("div","muigui-pop-down-bottom")),this.setOptions(i)}setKnobColor(t){this.#b&&(this.#b.style=`\n --range-color: ${t};\n --value-bg-color: ${t};\n `)}updateDisplay(){super.updateDisplay();const{open:t}=this.#u;this.domElement.children[1].classList.toggle("muigui-open",t),this.domElement.children[1].classList.toggle("muigui-closed",!t)}setOptions(t){l(this.#u,t),super.setOptions(t),this.updateDisplay()}addTop(t){return this.#Z.add(t)}addBottom(t){return this.#Q.add(t)}}class ue extends le{#A;#S;#$;constructor(t,e,n={}){super(t,e,"muigui-color-chooser");const i=n.format||St(this.getValue()),{color:o,text:r}=qt[i];this.#$=o.to,this.#S=new G(this,{converters:r,alpha:It(i)}),this.#A=new ae(this,{converters:o,alpha:It(i)}),this.addTop(this.#S),this.addBottom(this.#A),this.__setKnobHelper=()=>{if(this.#$){const t=this.#$(this.getValue()),e=yt(Z(t));e[2]=(e[2]+50)%100;const n=Q(vt(e));this.setKnobColor(`${t.substring(0,7)}FF`,n)}},this.updateDisplay()}updateDisplay(){super.updateDisplay(),this.__setKnobHelper&&this.__setKnobHelper()}setOptions(t){return super.setOptions(t),this}}class ce extends ee{add(t,e,...n){const i=t instanceof g?t:function(t,e,...n){const[i]=n;if(Array.isArray(i))return new z(t,e,{keyValues:i});const o=typeof t[e];switch(o){case"number":if("number"==typeof n[0]&&"number"==typeof n[1]){const i=n[0],o=n[1],r=n[2];return new B(t,e,{min:i,max:o,...r&&{step:r}})}return 0===n.length?new j(t,e,...n):new B(t,e,...n);case"boolean":return new N(t,e,...n);case"function":return new f(t,e,...n);case"string":return new R(t,e,...n);case"undefined":throw new Error(`no property named ${e}`);default:throw new Error(`unhandled type ${o} for property ${e}`)}}(t,e,...n);return this.addController(i)}addCanvas(t){return this.addController(new Jt(t))}addColor(t,e,n={}){const i=t[e];return It(n.format||St(i))?this.addController(new ue(t,e,n)):this.addController(new Zt(t,e,n))}addDivider(){return this.addController(new Qt)}addFolder(t){return this.addController(new ce(t))}addLabel(t){return this.addController(new ne(t))}}class he extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"})}}customElements.define("muigui-element",he);const de=new CSSStyleSheet;de.replaceSync(t.default);const pe=new CSSStyleSheet;function me(t){let e,n;function i(){if(e&&!n){const o=e;e=void 0,n=t.replace(o).then((()=>{n=void 0,i()}))}}return function(t){e=t,i()}}const ge=me(de),fe=me(pe);class be extends ce{static converters=U;static mapRange=u;static makeRangeConverters=c;static makeRangeOptions=h;static makeMinMaxPair=p;#tt=new CSSStyleSheet;constructor(t={}){super("Controls","muigui-root"),t instanceof HTMLElement&&(t={parent:t});const{autoPlace:n=!0,width:i,title:o="Controls"}=t;let{parent:r}=t;if(i&&(this.domElement.style.width=/^\d+$/.test(i)?`${i}px`:i),void 0===r&&n&&(r=document.body,this.domElement.classList.add("muigui-auto-place")),r){const t=e("muigui-element");t.shadowRoot.adoptedStyleSheets=[de,pe,this.#tt],t.shadow.appendChild(this.domElement),r.appendChild(t)}o&&this.title(o),this.domElement.classList.add("muigui","muigui-colors")}setStyle(t){this.#tt.replace(t)}static setBaseStyles(t){ge(t)}static getBaseStyleSheet(){return de}static setUserStyles(t){fe(t)}static getUserStyleSheet(){return pe}static setTheme(e){be.setBaseStyles(`${t.default}\n${t.themes[e]||""}`)}}function ve(){}const xe={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]};function we(t,{onDown:e=ve,onUp:n=ve}){const i=function(t){const i=t.shiftKey?10:1,[o,r]=(xe[t.key]||[0,0]).map((t=>t*i));("keydown"===t.type?e:n)({type:t.type.substring(3),dx:o,dy:r,event:t})};return t.addEventListener("keydown",i),t.addEventListener("keyup",i),function(){t.removeEventListener("keydown",i),t.removeEventListener("keyup",i)}}function ye(t,e=""){if(!t)throw new Error(e)}function ke(t,e,n,i,o,r){const s=Math.abs(n)*Math.cos(r),a=Math.abs(i)*Math.sin(r);return[t+Math.cos(o)*s-Math.sin(o)*a,e+Math.sin(o)*s+Math.cos(o)*a]}function Ee(t,e,n,i,o){ye(Math.abs(i-o)<=2*Math.PI),ye(i>=-Math.PI&&i<=2*Math.PI),ye(i<=o),ye(o>=-Math.PI&&o<=4*Math.PI);const{x1:r,y1:s,x2:a,y2:l,fa:u,fs:c}=function(t,e,n,i,o,r,s){const[a,l]=ke(t,e,n,i,o,r),[u,c]=ke(t,e,n,i,o,r+s);return{x1:a,y1:l,x2:u,y2:c,fa:Math.abs(s)>Math.PI?1:0,fs:s>0?1:0}}(t,e,n,n,0,i,o-i);return Math.abs(Math.abs(i-o)-2*Math.PI)>Number.EPSILON?`M${t} ${e} L${r} ${s} A ${n} ${n} 0 ${u} ${c} ${a} ${l} L${t} ${e}`:`M${r} ${s} L${r} ${s} A ${n} ${n} 0 ${u} ${c} ${a} ${l}`}const $e=t=>a(t+Math.PI,2*Math.PI)-Math.PI;class Ce extends v{#et;#nt;#it;#ot;#u={step:1,min:-180,max:180,dirMin:-Math.PI,dirMax:Math.PI,wrap:void 0,converters:F};constructor(t,n={}){const i=L();super(e("div",{className:"muigui-direction muigui-no-scroll",innerHTML:'\n\n',onWheel:e=>{e.preventDefault();const{min:n,max:r,step:l}=this.#u,u=i(e,l);let c=this.#it+u;this.#ot&&(c=a(c-n,r-n)+n);const h=o(s(c,(t=>t),l),n,r);t.setValue(h)}}));const r=e=>{const{min:n,max:i,step:r,dirMin:a,dirMax:l}=this.#u,u=2*e.nx-1,c=2*e.ny-1,h=Math.atan2(c,u),d=(a+l)/2,p=o(($e(h-d)-$e(a-d))/(l-a),0,1),m=s(n+(i-n)*p,(t=>t),r);t.setValue(m)};re(this.domElement,{onDown:r,onMove:r}),we(this.domElement,{onDown:e=>{const{min:n,max:i,step:r}=this.#u,a=o(s(this.#it+e.dx*r,(t=>t),r),n,i);t.setValue(a)}}),this.#et=this.$("#muigui-arrow"),this.#nt=this.$("#muigui-range"),this.setOptions(n)}updateDisplay(t){this.#it=t;const{min:e,max:n}=this.#u,i=(t-e)/(n-e),o=(r=this.#u.dirMin,s=this.#u.dirMax,r+(s-r)*i);var r,s;this.#et.style.transform=`rotate(${o}rad)`}setOptions(t){l(this.#u,t);const{dirMin:e,dirMax:n,wrap:i}=this.#u;this.#ot=void 0!==i?i:Math.abs(e-n)>=2*Math.PI-Number.EPSILON;const[o,r]=e(o.push(i),e("label",{},[e("input",{type:"radio",name:r,value:a,onChange:function(){this.checked&&t.setFinalValue(s.#D[this.value])}}),e("button",{type:"button",textContent:n,onClick:function(){this.previousElementSibling.click()}})]))))));const s=this;this.#D=o,this.cols(i)}updateDisplay(t){const e=this.#D.indexOf(t);for(let t=0;t{e({rect:t.getBoundingClientRect(),elem:t})})).observe(t)}function Me(t,e,n,i){Ie(t,(({rect:o})=>{const{width:r,height:s}=o;t.setAttribute("viewBox",`-${r*e} -${s*n} ${r} ${s}`),i({elem:t,rect:o})}))}function Se(t,e,n,i,o,r){const a=[];tt),n)),e=Math.min(e,o);for(let i=t;i<=e;i+=n)a.push(`M${i} 0 l0 ${r}`);return a.join(" ")}class De extends v{#rt;#st;#at;#lt;#ut;#ct;#ht;#dt;#pt;#it;#mt;#u={min:-100,max:100,step:1,unit:10,unitSize:10,ticksPerUnit:5,labelFn:t=>t,tickHeight:1,limits:!0,thicksColor:void 0,orientation:void 0};constructor(t,n){const i=L();let r;super(e("div",{innerHTML:'\n\n',className:"muigui-no-v-scroll",onWheel:e=>{e.preventDefault();const{min:n,max:r,step:a}=this.#u,l=i(e,a),u=o(s(this.#it+l,(t=>t),a),n,r);t.setValue(u)}})),this.#rt=this.$("svg"),this.#st=this.$("#muigui-origin"),this.#at=this.$("#muigui-ticks"),this.#lt=this.$("#muigui-thicks"),this.#ut=this.$("#muigui-numbers"),this.#ct=this.$("#muigui-left-grad"),this.#ht=this.$("#muigui-right-grad"),this.setOptions(n),re(this.domElement,{onDown:()=>{r=this.#it},onMove:e=>{const{min:n,max:i,unitSize:a,unit:l,step:u}=this.#u,c=o(s(r-e.dx/a*l,(t=>t),u),n,i);t.setValue(c)}}),we(this.domElement,{onDown:e=>{const{min:n,max:i,step:r}=this.#u,a=o(s(this.#it+e.dx*r,(t=>t),r),n,i);t.setValue(a)}}),Me(this.#rt,.5,0,(({rect:{width:t}})=>{this.#ct.setAttribute("x",-t/2),this.#ht.setAttribute("x",t/2-20),this.#mt=function(t){const e=t.innerHTML;t.innerHTML="- ";const n=t.querySelector("text").getComputedTextLength();return t.innerHTML=e,n}(this.#ut),this.#dt=t,this.#gt()}))}#gt(){if(!this.#dt||void 0===this.#it)return;const{labelFn:t,limits:e,min:n,max:i,orientation:o,tickHeight:r,ticksPerUnit:a,unit:l,unitSize:u,thicksColor:c}=this.#u,h=Math.ceil(this.#dt/u),d=this.#it/l,p=Math.round(d-h),m=p*u,g=(p+2*h)*u,f=e?n*u/l:m,b=e?i*u/l:g,v=""===t(1)?10:5;a>1&&this.#at.setAttribute("d",Se(m,g,u/a,f,b,v*r)),this.#lt.style.stroke=c,this.#lt.setAttribute("d",Se(m,g,u,f,b,v)),this.#ut.innerHTML=function(t,e,n,i,o,r,a,l){const u=[];tt),n)),e=Math.min(e,a);const c=Math.max(0,-Math.log10(i));for(let r=t;r<=e;r+=n)u.push(`${h=r/n*i,l(h.toFixed(c))}`);var h;return u.join("\n")}(m,g,u,l,this.#mt,f,b,t),this.#st.setAttribute("transform",`translate(${-this.#it*u/l} 0)`),this.#rt.classList.toggle("muigui-slider-up","up"===o)}updateDisplay(t){this.#it=t,this.#gt()}setOptions(t){return l(this.#u,t),this}}class Ne extends v{#rt;#et;#T;#it=[];constructor(t){super(e("div",{innerHTML:'\n\n',className:"muigui-no-scroll"}));const n=e=>{const{width:n,height:i}=this.#rt.getBoundingClientRect(),o=2*e.nx-1,r=2*e.ny-1;t.setValue([o*n*.5,r*i*.5])};re(this.domElement,{onDown:n,onMove:n}),this.#rt=this.$("svg"),this.#et=this.$("#muigui-arrow"),this.#T=this.$("#muigui-circle"),Me(this.#rt,.5,.5,(()=>this.#ft))}#ft(){const[t,e]=this.#it;this.#et.setAttribute("d",`M0,0L${t},${e}`),this.#T.setAttribute("transform",`translate(${t}, ${e})`)}updateDisplay(t){this.#it[0]=t[0],this.#it[1]=t[1],this.#ft()}}return be.ColorChooser=ue,be.Direction=class extends le{#u;constructor(t,e,n){super(t,e,"muigui-direction"),this.#u=n,this.addTop(new O(this,F)),this.addBottom(new Ce(this,n)),this.updateDisplay()}},be.RadioGrid=class extends D{constructor(t,e,n){super(t,e,"muigui-radio-grid");const i="number"==typeof this.getValue(),{keyValues:o,cols:r=3}=n,s=H(o,i);this.add(new Ve(this,s,r)),this.updateDisplay()}},be.Range=B,be.Select=z,be.Slider=class extends D{constructor(t,e,n={}){super(t,e,"muigui-slider"),this.add(new De(this,n)),this.add(new O(this,n)),this.updateDisplay()}},be.TextNumber=j,be.Vec2=class extends le{constructor(t,e){super(t,e,"muigui-vec2");const n=t=>({setValue:e=>{const n=this.getValue();n[t]=e,this.setValue(n)},setFinalValue:e=>{const n=this.getValue();n[t]=e,this.setFinalValue(n)}});this.addTop(new O(n(0),{converters:{to:t=>t[0],from:A.from}})),this.addTop(new O(n(1),{converters:{to:t=>t[1],from:A.from}})),this.addBottom(new Ne(this)),this.updateDisplay()}},be}));
-//# sourceMappingURL=muigui.min.js.map
diff --git a/dist/0.x/muigui.module.js b/dist/0.x/muigui.module.js
deleted file mode 100644
index e0992db..0000000
--- a/dist/0.x/muigui.module.js
+++ /dev/null
@@ -1,3814 +0,0 @@
-/* muigui@0.0.10, license MIT */
-var css = {
- default: `
-.muigui {
- --bg-color: #ddd;
- --color: #222;
- --contrast-color: #eee;
- --value-color: #145 ;
- --value-bg-color: #eeee;
- --disabled-color: #999;
- --menu-bg-color: #f8f8f8;
- --menu-sep-color: #bbb;
- --hover-bg-color: #999;
- --focus-color: #68C;
- --range-color: #888888;
- --invalid-color: #FF0000;
- --selected-color: rgb(255, 255, 255, 0.9);
-
- --button-bg-color: var(--value-bg-color);
-
- --range-left-color: var(--value-color);
- --range-right-color: var(--value-bg-color);
- --range-right-hover-color: var(--hover-bg-color);
-
- color: var(--color);
- background-color: var(--bg-color);
-}
-
-@media (prefers-color-scheme: dark) {
- .muigui {
- --bg-color: #222222;
- --color: #dddddd;
- --contrast-color: #000;
- --value-color: #43e5f7;
- --value-bg-color: #444444;
- --disabled-color: #666666;
- --menu-bg-color: #080808;
- --menu-sep-color: #444444;
- --hover-bg-color: #666666;
- --focus-color: #88AAFF;
- --range-color: #888888;
- --invalid-color: #FF6666;
- --selected-color: rgba(255, 255, 255, 0.3);
-
- --button-bg-color: var(--value-bg-color);
-
- --range-left-color: var(--value-color);
- --range-right-color: var(--value-bg-color);
- --range-right-hover-color: var(--hover-bg-color);
-
- color: var(--color);
- background-color: var(--bg-color);
- }
-}
-
-.muigui {
- --width: 250px;
- --label-width: 45%;
- --number-width: 40%;
-
-
- --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
- --font-size: 11px;
- --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace;
- --font-size-mono: 11px;
-
- --line-height: 1.7em;
- --border-radius: 0px;
-
- width: var(--width);
- font-family: var(--font-family);
- font-size: var(--font-size);
- box-sizing: border-box;
- line-height: 100%;
-}
-.muigui * {
- box-sizing: inherit;
-}
-
-.muigui-no-scroll {
- touch-action: none;
-}
-.muigui-no-h-scroll {
- touch-action: pan-y;
-}
-.muigui-no-v-scroll {
- touch-action: pan-x;
-}
-
-.muigui-invalid-value {
- background-color: red !important;
- color: white !important;
-}
-
-.muigui-grid {
- display: grid;
-}
-.muigui-rows {
- display: flex;
- flex-direction: column;
-
- min-height: 20px;
- border: 2px solid red;
-}
-.muigui-columns {
- display: flex;
- flex-direction: row;
-
- height: 20px;
- border: 2px solid green;
-}
-.muigui-rows>*,
-.muigui-columns>* {
- flex: 1 1 auto;
- align-items: stretch;
- min-height: 0;
- min-width: 0;
-}
-
-.muigui-row {
- border: 2px solid yellow;
- min-height: 10px
-}
-.muigui-column {
- border: 2px solid lightgreen;
-}
-
-/* -------- */
-
-.muigui-show { /* */ }
-.muigui-hide {
- display: none !important;
-}
-.muigui-disabled {
- pointer-events: none;
- --color: var(--disabled-color) !important;
- --value-color: var(--disabled-color) !important;
- --range-left-color: var(--disabled-color) !important;
-}
-
-.muigui canvas,
-.muigui svg {
- display: block;
- border-radius: var(--border-radius);
-}
-.muigui canvas {
- background-color: var(--value-bg-color);
-}
-
-.muigui-controller {
- min-width: 0;
- min-height: var(--line-height);
-}
-.muigui-root,
-.muigui-menu {
- display: flex;
- flex-direction: column;
- position: relative;
- user-select: none;
- height: fit-content;
- margin: 0;
- padding-bottom: 0.1em;
- border-radius: var(--border-radius);
-}
-.muigui-menu {
- border-bottom: 1px solid var(--menu-sep-color);
-}
-
-.muigui-root>button:nth-child(1),
-.muigui-menu>button:nth-child(1) {
- border-top: 1px solid var(--menu-sep-color);
- border-bottom: 1px solid var(--menu-sep-color);
- position: relative;
- text-align: left;
- color: var(--color);
- background-color: var(--menu-bg-color);
- min-height: var(--line-height);
- padding-top: 0.2em;
- padding-bottom: 0.2em;
- cursor: pointer;
- border-radius: var(--border-radius);
-}
-.muigui-root>div:nth-child(2),
-.muigui-menu>div:nth-child(2) {
- flex: 1 1 auto;
-}
-
-.muigui-controller {
- margin-left: 0.2em;
- margin-right: 0.2em;
-}
-.muigui-root.muigui-controller,
-.muigui-menu.muigui-controller {
- margin-left: 0;
- margin-right: 0;
-}
-.muigui-controller>*:nth-child(1) {
- flex: 1 0 var(--label-width);
- min-width: 0;
- white-space: pre;
-}
-.muigui-controller>label:nth-child(1) {
- place-content: center start;
- display: inline-grid;
- overflow: hidden;
-}
-.muigui-controller>*:nth-child(2) {
- flex: 1 1 75%;
- min-width: 0;
-}
-
-/* -----------------------------------------
- a label controller is [[label][value]]
-*/
-
-.muigui-label-controller {
- display: flex;
- margin: 0.4em 0 0.4em 0;
- word-wrap: initial;
- align-items: stretch;
-}
-
-.muigui-value {
- display: flex;
- align-items: stretch;
-}
-.muigui-value>* {
- flex: 1 1 auto;
- min-width: 0;
-}
-.muigui-value>*:nth-child(1) {
- flex: 1 1 calc(100% - var(--number-width));
-}
-.muigui-value>*:nth-child(2) {
- flex: 1 1 var(--number-width);
- margin-left: 0.2em;
-}
-
-/* fix! */
-.muigui-open>button>label::before,
-.muigui-closed>button>label::before {
- width: 1.25em;
- height: var(--line-height);
- display: inline-grid;
- place-content: center start;
- pointer-events: none;
-}
-.muigui-open>button>label::before {
- content: "โง"; /*"โผ";*/
-}
-.muigui-closed>button>label::before {
- content: "โจ"; /*"โถ";*/
-}
-.muigui-open>*:nth-child(2) {
- transition: max-height 0.2s ease-out,
- opacity 0.5s ease-out;
- max-height: 100vh;
- overflow: auto;
- opacity: 1;
-}
-
-.muigui-closed>*:nth-child(2) {
- transition: max-height 0.2s ease-out,
- opacity 1s;
- max-height: 0;
- opacity: 0;
- overflow: hidden;
-}
-
-/* ---- popdown ---- */
-
-.muigui-pop-down-top {
- display: flex;
-}
-/* fix? */
-.muigui-value>*:nth-child(1).muigui-pop-down-top {
- flex: 0;
-}
-.muigui-pop-down-bottom {
-
-}
-
-.muigui-pop-down-values {
- min-width: 0;
- display: flex;
-}
-.muigui-pop-down-values>* {
- flex: 1 1 auto;
- min-width: 0;
-}
-
-.muigui-value.muigui-pop-down-controller {
- flex-direction: column;
-}
-
-.muigui-pop-down-top input[type=checkbox] {
- -webkit-appearance: none;
- appearance: none;
- width: auto;
- color: var(--value-color);
- background-color: var(--value-bg-color);
- cursor: pointer;
-
- display: grid;
- place-content: center;
- margin: 0;
- font: inherit;
- color: currentColor;
- width: 1.7em;
- height: 1.7em;
- transform: translateY(-0.075em);
-}
-
-.muigui-pop-down-top input[type=checkbox]::before {
- content: "+";
- display: grid;
- place-content: center;
- border-radius: calc(var(--border-radius) + 2px);
- border-left: 1px solid rgba(255,255,255,0.3);
- border-top: 1px solid rgba(255,255,255,0.3);
- border-bottom: 1px solid rgba(0,0,0,0.2);
- border-right: 1px solid rgba(0,0,0,0.2);
- background-color: var(--range-color);
- color: var(--value-bg-color);
- width: calc(var(--line-height) - 4px);
- height: calc(var(--line-height) - 4px);
-}
-
-.muigui-pop-down-top input[type=checkbox]:checked::before {
- content: "๏ผธ";
-}
-
-
-/* ---- select ---- */
-
-.muigui select,
-.muigui option,
-.muigui input,
-.muigui button {
- color: var(--value-color);
- background-color: var(--value-bg-color);
- font-family: var(--font-family);
- font-size: var(--font-size);
- border: none;
- margin: 0;
- border-radius: var(--border-radius);
-}
-.muigui select {
- appearance: none;
- margin: 0;
- margin-left: 0; /*?*/
- overflow: hidden; /* Safari */
-}
-
-.muigui select:focus,
-.muigui input:focus,
-.muigui button:focus {
- outline: 1px solid var(--focus-color);
-}
-
-.muigui select:hover,
-.muigui option:hover,
-.muigui input:hover,
-.muigui button:hover {
- background-color: var(--hover-bg-color);
-}
-
-/* ------ [ label ] ------ */
-
-.muigui-label {
- border-top: 1px solid var(--menu-sep-color);
- border-bottom: 1px solid var(--menu-sep-color);
- padding-top: 0.4em;
- padding-bottom: 0.3em;
- place-content: center start;
- background-color: var(--menu-bg-color);
- white-space: pre;
- border-radius: var(--border-radius);
-}
-
-/* ------ [ divider] ------ */
-
-.muigui-divider {
- min-height: 6px;
- border-top: 2px solid var(--menu-sep-color);
- margin-top: 6px;
-}
-
-/* ------ [ button ] ------ */
-
-.muigui-button {
- display: grid;
-
-}
-.muigui-button button {
- border: none;
- color: var(--value-color);
- background-color: var(--button-bg-color);
- cursor: pointer;
- place-content: center center;
-}
-
-/* ------ [ color ] ------ */
-
-.muigui-color>div {
- overflow: hidden;
- position: relative;
- margin-left: 0;
- margin-right: 0; /* why? */
- max-width: var(--line-height);
- border-radius: var(--border-radius);
-}
-
-.muigui-color>div:focus-within {
- outline: 1px solid var(--focus-color);
-}
-
-.muigui-color input[type=color] {
- border: none;
- padding: 0;
- background: inherit;
- cursor: pointer;
- position: absolute;
- width: 200%;
- left: -10px;
- top: -10px;
- height: 200%;
-}
-.muigui-disabled canvas,
-.muigui-disabled svg,
-.muigui-disabled img,
-.muigui-disabled .muigui-color input[type=color] {
- opacity: 0.2;
-}
-
-/* ------ [ checkbox ] ------ */
-
-.muigui-checkbox>label:nth-child(2) {
- display: grid;
- place-content: center start;
- margin: 0;
-}
-
-.muigui-checkbox input[type=checkbox] {
- -webkit-appearance: none;
- appearance: none;
- width: auto;
- color: var(--value-color);
- background-color: var(--value-bg-color);
- cursor: pointer;
-
- display: grid;
- place-content: center;
- margin: 0;
- font: inherit;
- color: currentColor;
- width: 1.7em;
- height: 1.7em;
- transform: translateY(-0.075em);
-}
-
-.muigui-checkbox input[type=checkbox]::before {
- content: "";
- color: var(--value-color);
- display: grid;
- place-content: center;
-}
-
-.muigui-checkbox input[type=checkbox]:checked::before {
- content: "โ";
-}
-
-.muigui input[type=number]::-webkit-inner-spin-button,
-.muigui input[type=number]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- appearance: none;
- margin: 0;
-}
-.muigui input[type=number] {
- -moz-appearance: textfield;
-}
-
-/* ------ [ radio grid ] ------ */
-
-.muigui-radio-grid>div {
- display: grid;
- gap: 2px;
-}
-
-.muigui-radio-grid input {
- appearance: none;
- display: none;
-}
-
-.muigui-radio-grid button {
- color: var(--color);
- width: 100%;
- text-align: left;
-}
-
-.muigui-radio-grid input:checked + button {
- color: var(--value-color);
- background-color: var(--selected-color);
-}
-
-/* ------ [ color-chooser ] ------ */
-
-.muigui-color-chooser-cursor {
- stroke-width: 1px;
- stroke: white;
- fill: none;
-}
-.muigui-color-chooser-circle {
- stroke-width: 1px;
- stroke: white;
- fill: none;
-}
-
-
-/* ------ [ vec2 ] ------ */
-
-.muigui-vec2 svg {
- background-color: var(--value-bg-color);
-}
-
-.muigui-vec2-axis {
- stroke: 1px;
- stroke: var(--focus-color);
-}
-
-.muigui-vec2-line {
- stroke-width: 1px;
- stroke: var(--value-color);
- fill: var(--value-color);
-}
-
-/* ------ [ direction ] ------ */
-
-.muigui-direction svg {
- background-color: rgba(0,0,0,0.2);
-}
-
-.muigui-direction:focus-within svg {
- outline: none;
-}
-.muigui-direction-range {
- fill: var(--value-bg-color);
-}
-.muigui-direction svg:focus {
- outline: none;
-}
-.muigui-direction svg:focus .muigui-direction-range {
- stroke-width: 0.5px;
- stroke: var(--focus-color);
-}
-
-.muigui-direction-arrow {
- fill: var(--value-color);
-}
-
-/* ------ [ slider ] ------ */
-
-.muigui-slider>div {
- display: flex;
- align-items: stretch;
- height: var(--line-height);
-}
-.muigui-slider svg {
- flex: 1 1 auto;
-}
-.muigui-slider .muigui-slider-up #muigui-orientation {
- transform: scale(1, -1) translateY(-100%);
-}
-
-.muigui-slider .muigui-slider-up #muigui-number-orientation {
- transform: scale(1,-1);
-}
-
-.muigui-ticks {
- stroke: var(--range-color);
-}
-.muigui-thicks {
- stroke: var(--color);
- stroke-width: 2px;
-}
-.muigui-svg-text {
- fill: var(--color);
- font-size: 7px;
-}
-.muigui-mark {
- fill: var(--value-color);
-}
-
-/* ------ [ range ] ------ */
-
-
-.muigui-range input[type=range] {
- -webkit-appearance: none;
- appearance: none;
- background-color: transparent;
-}
-
-.muigui-range input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- border-radius: calc(var(--border-radius) + 2px);
- border-left: 1px solid rgba(255,255,255,0.3);
- border-top: 1px solid rgba(255,255,255,0.3);
- border-bottom: 1px solid rgba(0,0,0,0.2);
- border-right: 1px solid rgba(0,0,0,0.2);
- background-color: var(--range-color);
- margin-top: calc((var(--line-height) - 2px) / -2);
- width: calc(var(--line-height) - 2px);
- height: calc(var(--line-height) - 2px);
-}
-
-.muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: none;
- appearance: none;
- border: 1px solid var(--menu-sep-color);
- height: 2px;
-}
-
-
-/* dat.gui style - doesn't work on Safari iOS */
-
-/*
-.muigui-range input[type=range] {
- cursor: ew-resize;
- overflow: hidden;
-}
-
-.muigui-range input[type=range] {
- -webkit-appearance: none;
- appearance: none;
- background-color: var(--range-right-color);
- margin: 0;
-}
-.muigui-range input[type=range]:hover {
- background-color: var(--range-right-hover-color);
-}
-
-.muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: none;
- appearance: none;
- height: max-content;
- color: var(--range-left-color);
- margin-top: -1px;
-}
-
-.muigui-range input[type=range]::-webkit-slider-thumb {
- -webkit-appearance: none;
- appearance: none;
- width: 0px;
- height: max-content;
- box-shadow: -1000px 0 0 1000px var(--range-left-color);
-}
-*/
-
-/* FF */
-/*
-.muigui-range input[type=range]::-moz-slider-progress {
- background-color: var(--range-left-color);
-}
-.muigui-range input[type=range]::-moz-slider-thumb {
- height: max-content;
- width: 0;
- border: none;
- box-shadow: -1000px 0 0 1000px var(--range-left-color);
- box-sizing: border-box;
-}
-*/
-
-.muigui-checkered-background {
- background-color: #404040;
- background-image:
- linear-gradient(45deg, #808080 25%, transparent 25%),
- linear-gradient(-45deg, #808080 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, #808080 75%),
- linear-gradient(-45deg, transparent 75%, #808080 75%);
- background-size: 16px 16px;
- background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
-}
-
-/* ---------------------------------------------------------- */
-
-/* needs to be at bottom to take precedence */
-.muigui-auto-place {
- max-height: 100%;
- position: fixed;
- top: 0;
- right: 15px;
- z-index: 100001;
-}
-
-`,
-themes: {
- default: '',
- float: `
- :root {
- color-scheme: light dark,
- }
-
- .muigui {
- --width: 400px;
- --bg-color: initial;
- --label-width: 25%;
- --number-width: 20%;
- }
-
- input,
- .muigui-label-controller>label {
- text-shadow:
- -1px -1px 0 var(--contrast-color),
- 1px -1px 0 var(--contrast-color),
- -1px 1px 0 var(--contrast-color),
- 1px 1px 0 var(--contrast-color);
- }
-
- .muigui-controller > label:nth-child(1) {
- place-content: center end;
- margin-right: 1em;
- }
-
- .muigui-value > :nth-child(2) {
- margin-left: 1em;
- }
-
- .muigui-root>*:nth-child(1) {
- display: none;
- }
-
- .muigui-range input[type=range]::-webkit-slider-thumb {
- border-radius: 1em;
- }
-
- .muigui-range input[type=range]::-webkit-slider-runnable-track {
- -webkit-appearance: initial;
- appearance: none;
- border: 1px solid rgba(0, 0, 0, 0.25);
- height: 2px;
- }
-
- .muigui-colors {
- --value-color: var(--color );
- --value-bg-color: rgba(0, 0, 0, 0.1);
- --disabled-color: #cccccc;
- --menu-bg-color: rgba(0, 0, 0, 0.1);
- --menu-sep-color: #bbbbbb;
- --hover-bg-color: rgba(0, 0, 0, 0);
- --invalid-color: #FF0000;
- --selected-color: rgba(0, 0, 0, 0.3);
- --range-color: rgba(0, 0, 0, 0.125);
- }
-`,
-},
-};
-
-function setElemProps(elem, attrs, children) {
- for (const [key, value] of Object.entries(attrs)) {
- if (typeof value === 'function' && key.startsWith('on')) {
- const eventName = key.substring(2).toLowerCase();
- elem.addEventListener(eventName, value, {passive: false});
- } else if (typeof value === 'object') {
- for (const [k, v] of Object.entries(value)) {
- elem[key][k] = v;
- }
- } else if (elem[key] === undefined) {
- elem.setAttribute(key, value);
- } else {
- elem[key] = value;
- }
- }
- for (const child of children) {
- elem.appendChild(child);
- }
- return elem;
-}
-
-function createElem(tag, attrs = {}, children = []) {
- const elem = document.createElement(tag);
- setElemProps(elem, attrs, children);
- return elem;
-}
-
-function addElem(tag, parent, attrs = {}, children = []) {
- const elem = createElem(tag, attrs, children);
- parent.appendChild(elem);
- return elem;
-}
-
-let nextId = 0;
-function getNewId() {
- return `muigui-id-${nextId++}`;
-}
-
-function removeArrayElem(array, value) {
- const ndx = array.indexOf(value);
- if (ndx) {
- array.splice(ndx, 1);
- }
- return array;
-}
-
-/**
- * Converts an camelCase or snake_case id to "camel case" or "snake case"
- * @param {string} id
- */
-const underscoreRE = /_/g;
-const upperLowerRE = /([A-Z])([a-z])/g;
-function idToLabel(id) {
- return id.replace(underscoreRE, ' ')
- .replace(upperLowerRE, (m, m1, m2) => `${m1.toLowerCase()} ${m2}`);
-}
-
-function clamp$1(v, min, max) {
- return Math.max(min, Math.min(max, v));
-}
-
-const isTypedArray = typeof SharedArrayBuffer !== 'undefined'
- ? function isArrayBufferOrSharedArrayBuffer(a) {
- return a && a.buffer && (a.buffer instanceof ArrayBuffer || a.buffer instanceof SharedArrayBuffer);
- }
- : function isArrayBuffer(a) {
- return a && a.buffer && a.buffer instanceof ArrayBuffer;
- };
-
-const isArrayOrTypedArray = v => Array.isArray(v) || isTypedArray(v);
-
-// Yea, I know this should be `Math.round(v / step) * step
-// but try step = 0.1, newV = 19.95
-//
-// I get
-// Math.round(19.95 / 0.1) * 0.1
-// 19.900000000000002
-// vs
-// Math.round(19.95 / 0.1) / (1 / 0.1)
-// 19.9
-//
-const stepify = (v, from, step) => Math.round(from(v) / step) / (1 / step);
-
-const euclideanModulo$1 = (v, n) => ((v % n) + n) % n;
-const lerp$1 = (a, b, t) => a + (b - a) * t;
-function copyExistingProperties(dst, src) {
- for (const key in src) {
- if (key in dst) {
- dst[key] = src[key];
- }
- }
- return dst;
-}
-
-const mapRange = (v, inMin, inMax, outMin, outMax) => (v - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
-
-const makeRangeConverters = ({from, to}) => {
- return {
- to: v => mapRange(v, ...from, ...to),
- from: v => [true, mapRange(v, ...to, ...from)],
- };
-};
-
-const makeRangeOptions = ({from, to, step}) => {
- return {
- min: to[0],
- max: to[1],
- ...(step && {step}),
- converters: makeRangeConverters({from, to}),
- };
-};
-
-// TODO: remove an use one in conversions. Move makeRangeConverters there?
-const identity$1 = {
- to: v => v,
- from: v => [true, v],
-};
-function makeMinMaxPair(gui, properties, minPropName, maxPropName, options) {
- const { converters: { from } = identity$1 } = options;
- const { min, max } = options;
- const guiMinRange = options.minRange || 0;
- const valueMinRange = from(guiMinRange)[1];
- const minGui = gui
- .add(properties, minPropName, {
- ...options,
- min,
- max: max - guiMinRange,
- })
- .onChange(v => {
- maxGui.setValue(Math.min(max, Math.max(v + valueMinRange, properties[maxPropName])));
- });
- const maxGui = gui
- .add(properties, maxPropName, {
- ...options,
- min: min + guiMinRange,
- max,
- })
- .onChange(v => {
- minGui.setValue(Math.max(min, Math.min(v - valueMinRange, properties[minPropName])));
- });
- return [ minGui, maxGui ];
-}
-
-class View {
- domElement;
- #childDestElem;
- #views = [];
- constructor(elem) {
- this.domElement = elem;
- this.#childDestElem = elem;
- }
- addElem(elem) {
- this.#childDestElem.appendChild(elem);
- return elem;
- }
- removeElem(elem) {
- this.#childDestElem.removeChild(elem);
- return elem;
- }
- pushSubElem(elem) {
- this.#childDestElem.appendChild(elem);
- this.#childDestElem = elem;
- }
- popSubElem() {
- this.#childDestElem = this.#childDestElem.parentElement;
- }
- add(view) {
- this.#views.push(view);
- this.addElem(view.domElement);
- return view;
- }
- remove(view) {
- this.removeElem(view.domElement);
- removeArrayElem(this.#views, view);
- return view;
- }
- pushSubView(view) {
- this.pushSubElem(view.domElement);
- }
- popSubView() {
- this.popSubElem();
- }
- setOptions(options) {
- for (const view of this.#views) {
- view.setOptions(options);
- }
- }
- updateDisplayIfNeeded(newV, ignoreCache) {
- for (const view of this.#views) {
- view.updateDisplayIfNeeded(newV, ignoreCache);
- }
- return this;
- }
- $(selector) {
- return this.domElement.querySelector(selector);
- }
-}
-
-class Controller extends View {
- #changeFns;
- #finishChangeFns;
- #parent;
-
- constructor(className) {
- super(createElem('div', {className: 'muigui-controller'}));
- this.#changeFns = [];
- this.#finishChangeFns = [];
- // we need the specialization to come last so it takes precedence.
- if (className) {
- this.domElement.classList.add(className);
- }
- }
- get parent() {
- return this.#parent;
- }
- setParent(parent) {
- this.#parent = parent;
- this.enable(!this.disabled());
- }
- show(show = true) {
- this.domElement.classList.toggle('muigui-hide', !show);
- this.domElement.classList.toggle('muigui-show', show);
- return this;
- }
- hide() {
- return this.show(false);
- }
- disabled() {
- return !!this.domElement.closest('.muigui-disabled');
- }
-
- enable(enable = true) {
- this.domElement.classList.toggle('muigui-disabled', !enable);
-
- // If disabled we need to set the attribute 'disabled=true' to all
- // input/select/button/textarea's below
- //
- // If enabled we need to set the attribute 'disabled=false' to all below
- // until we hit a disabled controller.
- //
- // ATM the problem is we can find the input/select/button/textarea elements
- // but we can't easily find which controller they belong do.
- // But we don't need to? We can just check up if it or parent has
- // '.muigui-disabled'
- ['input', 'button', 'select', 'textarea'].forEach(tag => {
- this.domElement.querySelectorAll(tag).forEach(elem => {
- const disabled = !!elem.closest('.muigui-disabled');
- elem.disabled = disabled;
- });
- });
-
- return this;
- }
- disable(disable = true) {
- return this.enable(!disable);
- }
- onChange(fn) {
- this.removeChange(fn);
- this.#changeFns.push(fn);
- return this;
- }
- removeChange(fn) {
- removeArrayElem(this.#changeFns, fn);
- return this;
- }
- onFinishChange(fn) {
- this.removeFinishChange(fn);
- this.#finishChangeFns.push(fn);
- return this;
- }
- removeFinishChange(fn) {
- removeArrayElem(this.#finishChangeFns, fn);
- return this;
- }
- #callListeners(fns, newV) {
- for (const fn of fns) {
- fn.call(this, newV);
- }
- }
- emitChange(value, object, property) {
- this.#callListeners(this.#changeFns, value);
- if (this.#parent) {
- if (object === undefined) {
- this.#parent.emitChange(value);
- } else {
- this.#parent.emitChange({
- object,
- property,
- value,
- controller: this,
- });
- }
- }
- }
- emitFinalChange(value, object, property) {
- this.#callListeners(this.#finishChangeFns, value);
- if (this.#parent) {
- if (object === undefined) {
- this.#parent.emitChange(value);
- } else {
- this.#parent.emitFinalChange({
- object,
- property,
- value,
- controller: this,
- });
- }
- }
- }
- updateDisplay() {
- // placeholder. override
- }
- getColors() {
- const toCamelCase = s => s.replace(/-([a-z])/g, (m, m1) => m1.toUpperCase());
- const keys = [
- 'color',
- 'bg-color',
- 'value-color',
- 'value-bg-color',
- 'hover-bg-color',
- 'menu-bg-color',
- 'menu-sep-color',
- 'disabled-color',
- ];
- const div = createElem('div');
- this.domElement.appendChild(div);
- const colors = Object.fromEntries(keys.map(key => {
- div.style.color = `var(--${key})`;
- const s = getComputedStyle(div);
- return [toCamelCase(key), s.color];
- }));
- div.remove();
- return colors;
- }
-}
-
-class Button extends Controller {
- #object;
- #property;
- #buttonElem;
- #options = {
- name: '',
- };
-
- constructor(object, property, options = {}) {
- super('muigui-button', '');
- this.#object = object;
- this.#property = property;
-
- this.#buttonElem = this.addElem(
- createElem('button', {
- type: 'button',
- onClick: () => {
- this.#object[this.#property](this);
- },
- }));
- this.setOptions({name: property, ...options});
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {name} = this.#options;
- this.#buttonElem.textContent = name;
- }
-}
-
-function arraysEqual(a, b) {
- if (a.length !== b.length) {
- return false;
- }
- for (let i = 0; i < a.length; ++i) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
-}
-
-function copyArrayElementsFromTo(src, dst) {
- dst.length = src.length;
- for (let i = 0; i < src.length; ++i) {
- dst[i] = src[i];
- }
-}
-
-class EditView extends View {
- #oldV;
- #updateCheck;
-
- #checkArrayNeedsUpdate(newV) {
- // It's an array, we need to compare all elements
- // Example, vec2, [r,g,b], ...
- const needUpdate = !arraysEqual(newV, this.#oldV);
- if (needUpdate) {
- copyArrayElementsFromTo(newV, this.#oldV);
- }
- return needUpdate;
- }
-
- #checkTypedArrayNeedsUpdate() {
- let once = true;
- return function checkTypedArrayNeedsUpdateImpl(newV) {
- // It's a typedarray, we need to compare all elements
- // Example: Float32Array([r, g, b])
- let needUpdate = once;
- once = false;
- if (!needUpdate) {
- needUpdate = !arraysEqual(newV, this.#oldV);
- }
- return needUpdate;
- };
- }
-
- #checkObjectNeedsUpdate(newV) {
- let needUpdate = false;
- for (const key in newV) {
- if (newV[key] !== this.#oldV[key]) {
- needUpdate = true;
- this.#oldV[key] = newV[key];
- }
- }
- return needUpdate;
- }
-
- #checkValueNeedsUpdate(newV) {
- const needUpdate = newV !== this.#oldV;
- this.#oldV = newV;
- return needUpdate;
- }
-
- #getUpdateCheckForType(newV) {
- if (Array.isArray(newV)) {
- this.#oldV = [];
- return this.#checkArrayNeedsUpdate.bind(this);
- } else if (isTypedArray(newV)) {
- this.#oldV = new newV.constructor(newV);
- return this.#checkTypedArrayNeedsUpdate(this);
- } else if (typeof newV === 'object') {
- this.#oldV = {};
- return this.#checkObjectNeedsUpdate.bind(this);
- } else {
- return this.#checkValueNeedsUpdate.bind(this);
- }
- }
-
- // The point of this is updating DOM elements
- // is slow but if we've called `listen` then
- // every frame we're going to try to update
- // things with the current value so if nothing
- // has changed then skip it.
- updateDisplayIfNeeded(newV, ignoreCache) {
- this.#updateCheck = this.#updateCheck || this.#getUpdateCheckForType(newV);
- // Note: We call #updateCheck first because it updates
- // the cache
- if (this.#updateCheck(newV) || ignoreCache) {
- this.updateDisplay(newV);
- }
- }
- setOptions(/*options*/) {
- // override this
- return this;
- }
-}
-
-class CheckboxView extends EditView {
- #checkboxElem;
- constructor(setter, id) {
- const checkboxElem = createElem('input', {
- type: 'checkbox',
- id,
- onInput: () => {
- setter.setValue(checkboxElem.checked);
- },
- onChange: () => {
- setter.setFinalValue(checkboxElem.checked);
- },
- });
- super(createElem('label', {}, [checkboxElem]));
- this.#checkboxElem = checkboxElem;
- }
- updateDisplay(v) {
- this.#checkboxElem.checked = v;
- }
-}
-
-const tasks = [];
-const tasksToRemove = new Set();
-
-let requestId;
-let processing;
-
-function removeTasks() {
- if (!tasksToRemove.size) {
- return;
- }
-
- if (processing) {
- queueProcessing();
- return;
- }
-
- tasksToRemove.forEach(task => {
- removeArrayElem(tasks, task);
- });
- tasksToRemove.clear();
-}
-
-function processTasks() {
- requestId = undefined;
- processing = true;
- for (const task of tasks) {
- if (!tasksToRemove.has(task)) {
- task();
- }
- }
- processing = false;
- removeTasks();
- queueProcessing();
-}
-
-function queueProcessing() {
- if (!requestId && tasks.length) {
- requestId = requestAnimationFrame(processTasks);
- }
-}
-
-function addTask(fn) {
- tasks.push(fn);
- queueProcessing();
-}
-
-function removeTask(fn) {
- tasksToRemove.set(fn);
-
- const ndx = tasks.indexOf(fn);
- if (ndx >= 0) {
- tasks.splice(ndx, 1);
- }
-}
-
-let id = 0;
-
-function makeId() {
- return `muigui-${++id}`;
-}
-
-class ValueView extends View {
- constructor(className = '') {
- super(createElem('div', {className: 'muigui-value'}));
- if (className) {
- this.domElement.classList.add(className);
- }
- }
-}
-
-class LabelController extends Controller {
- #id;
- #nameElem;
-
- constructor(className = '', name = '') {
- super('muigui-label-controller');
- this.#id = makeId();
- this.#nameElem = createElem('label', {for: this.#id});
- this.domElement.appendChild(this.#nameElem);
- this.pushSubView(new ValueView(className));
- this.name(name);
- }
- get id() {
- return this.#id;
- }
- name(name) {
- if (this.#nameElem.title === this.#nameElem.textContent) {
- this.#nameElem.title = name;
- }
- this.#nameElem.textContent = name;
- return this;
- }
- tooltip(tip) {
- this.#nameElem.title = tip;
- }
-}
-
-class ValueController extends LabelController {
- #object;
- #property;
- #initialValue;
- #listening;
- #views;
- #updateFn;
-
- constructor(object, property, className = '') {
- super(className, property);
- this.#object = object;
- this.#property = property;
- this.#initialValue = this.getValue();
- this.#listening = false;
- this.#views = [];
- }
- get initialValue() {
- return this.#initialValue;
- }
- get object() {
- return this.#object;
- }
- get property() {
- return this.#property;
- }
- add(view) {
- this.#views.push(view);
- super.add(view);
- this.updateDisplay();
- return view;
- }
- #setValueImpl(v, ignoreCache) {
- let isDifferent = false;
- if (typeof v === 'object') {
- const dst = this.#object[this.#property];
- // don't replace objects, just their values.
- if (Array.isArray(v) || isTypedArray(v)) {
- for (let i = 0; i < v.length; ++i) {
- isDifferent ||= dst[i] !== v[i];
- dst[i] = v[i];
- }
- } else {
- for (const key of Object.keys(v)) {
- isDifferent ||= dst[key] !== v[key];
- }
- Object.assign(dst, v);
- }
- } else {
- isDifferent = this.#object[this.#property] !== v;
- this.#object[this.#property] = v;
- }
- this.updateDisplay(ignoreCache);
- if (isDifferent) {
- this.emitChange(this.getValue(), this.#object, this.#property);
- }
- return isDifferent;
- }
- setValue(v) {
- this.#setValueImpl(v);
- }
- setFinalValue(v) {
- const isDifferent = this.#setValueImpl(v, true);
- if (isDifferent) {
- this.emitFinalChange(this.getValue(), this.#object, this.#property);
- }
- return this;
- }
- updateDisplay(ignoreCache) {
- const newV = this.getValue();
- for (const view of this.#views) {
- view.updateDisplayIfNeeded(newV, ignoreCache);
- }
- return this;
- }
- setOptions(options) {
- for (const view of this.#views) {
- view.setOptions(options);
- }
- this.updateDisplay();
- return this;
- }
- getValue() {
- return this.#object[this.#property];
- }
- value(v) {
- this.setValue(v);
- return this;
- }
- reset() {
- this.setValue(this.#initialValue);
- return this;
- }
- listen(listen = true) {
- if (!this.#updateFn) {
- this.#updateFn = this.updateDisplay.bind(this);
- }
- if (listen) {
- if (!this.#listening) {
- this.#listening = true;
- addTask(this.#updateFn);
- }
- } else {
- if (this.#listening) {
- this.#listening = false;
- removeTask(this.#updateFn);
- }
- }
- return this;
- }
-}
-
-class Checkbox extends ValueController {
- constructor(object, property) {
- super(object, property, 'muigui-checkbox');
- const id = this.id;
- this.add(new CheckboxView(this, id));
- this.updateDisplay();
- }
-}
-
-const identity = {
- to: v => v,
- from: v => [true, v],
-};
-
-// from: from string to value
-// to: from value to string
-const strToNumber = {
- to: v => v.toString(),
- from: v => {
- const newV = parseFloat(v);
- return [!Number.isNaN(newV), newV];
- },
-};
-
-const converters = {
- radToDeg: makeRangeConverters({to: [0, 180], from: [0, Math.PI]}),
-};
-
-function createWheelHelper() {
- let wheelAccum = 0;
- return function(e, step, wheelScale = 5) {
- wheelAccum -= e.deltaY * step / wheelScale;
- const wheelSteps = Math.floor(Math.abs(wheelAccum) / step) * Math.sign(wheelAccum);
- const delta = wheelSteps * step;
- wheelAccum -= delta;
- return delta;
- };
-}
-
-class NumberView extends EditView {
- #to;
- #from;
- #step;
- #skipUpdate;
- #options = {
- step: 0.01,
- converters: strToNumber,
- min: Number.NEGATIVE_INFINITY,
- max: Number.POSITIVE_INFINITY,
- };
-
- constructor(setter, options) {
- const setValue = setter.setValue.bind(setter);
- const setFinalValue = setter.setFinalValue.bind(setter);
- const wheelHelper = createWheelHelper();
- super(createElem('input', {
- type: 'number',
- onInput: () => this.#handleInput(setValue, true),
- onChange: () => this.#handleInput(setFinalValue, false),
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.setOptions(options);
- }
- #handleInput(setFn, skipUpdate) {
- const v = parseFloat(this.domElement.value);
- const [valid, newV] = this.#from(v);
- let inRange;
- if (valid && !Number.isNaN(v)) {
- const {min, max} = this.#options;
- inRange = newV >= min && newV <= max;
- this.#skipUpdate = skipUpdate;
- setFn(clamp$1(newV, min, max));
- }
- this.domElement.classList.toggle('muigui-invalid-value', !valid || !inRange);
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = stepify(v, this.#to, this.#step);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- step,
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- this.#step = step;
- return this;
- }
-}
-
-// Wanted to name this `Number` but it conflicts with
-// JavaScript `Number`. It most likely wouldn't be
-// an issue? But users might `import {Number} ...` and
-// things would break.
-class TextNumber extends ValueController {
- #textView;
- #step;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-checkbox');
- this.#textView = this.add(new NumberView(this, options));
- this.updateDisplay();
- }
-}
-
-class SelectView extends EditView {
- #values;
-
- constructor(setter, keyValues) {
- const values = [];
- super(createElem('select', {
- onChange: () => {
- setter.setFinalValue(this.#values[this.domElement.selectedIndex]);
- },
- }, keyValues.map(([key, value]) => {
- values.push(value);
- return createElem('option', {textContent: key});
- })));
- this.#values = values;
- }
- updateDisplay(v) {
- const ndx = this.#values.indexOf(v);
- this.domElement.selectedIndex = ndx;
- }
-}
-
-// 4 cases
-// (a) keyValues is array of arrays, each sub array is key value
-// (b) keyValues is array and value is number then keys = array contents, value = index
-// (c) keyValues is array and value is not number, key = array contents, value = array contents
-// (d) keyValues is object then key->value
-function convertToKeyValues(keyValues, valueIsNumber) {
- if (Array.isArray(keyValues)) {
- if (Array.isArray(keyValues[0])) {
- // (a) keyValues is array of arrays, each sub array is key value
- return keyValues;
- } else {
- if (valueIsNumber) {
- // (b) keyValues is array and value is number then keys = array contents, value = index
- return keyValues.map((v, ndx) => [v, ndx]);
- } else {
- // (c) keyValues is array and value is not number, key = array contents, value = array contents
- return keyValues.map(v => [v, v]);
- }
- }
- } else {
- // (d)
- return [...Object.entries(keyValues)];
- }
-}
-
-class Select extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-select');
- const valueIsNumber = typeof this.getValue() === 'number';
- const {keyValues: keyValuesInput} = options;
- const keyValues = convertToKeyValues(keyValuesInput, valueIsNumber);
- this.add(new SelectView(this, keyValues));
- this.updateDisplay();
- }
-}
-
-class RangeView extends EditView {
- #to;
- #from;
- #step;
- #skipUpdate;
- #options = {
- step: 0.01,
- min: 0,
- max: 1,
- converters: identity,
- };
-
- constructor(setter, options) {
- const wheelHelper = createWheelHelper();
- super(createElem('input', {
- type: 'range',
- onInput: () => {
- this.#skipUpdate = true;
- const {min, max, step} = this.#options;
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v, v => v, step), min, max);
- const [valid, validV] = this.#from(newV);
- if (valid) {
- setter.setValue(validV);
- }
- },
- onChange: () => {
- this.#skipUpdate = true;
- const {min, max, step} = this.#options;
- const v = parseFloat(this.domElement.value);
- const newV = clamp$1(stepify(v, v => v, step), min, max);
- const [valid, validV] = this.#from(newV);
- if (valid) {
- setter.setFinalValue(validV);
- }
- },
- onWheel: e => {
- e.preventDefault();
- const [valid, v] = this.#from(parseFloat(this.domElement.value));
- if (!valid) {
- return;
- }
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const newV = clamp$1(stepify(v + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.setOptions(options);
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = stepify(v, this.#to, this.#step);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- step,
- min,
- max,
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- this.#step = step;
- this.domElement.step = step;
- this.domElement.min = min;
- this.domElement.max = max;
- return this;
- }
-}
-
-class Range extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-range');
- this.add(new RangeView(this, options));
- this.add(new NumberView(this, options));
- }
-}
-
-class TextView extends EditView {
- #to;
- #from;
- #skipUpdate;
- #options = {
- converters: identity,
- };
-
- constructor(setter, options) {
- const setValue = setter.setValue.bind(setter);
- const setFinalValue = setter.setFinalValue.bind(setter);
- super(createElem('input', {
- type: 'text',
- onInput: () => this.#handleInput(setValue, true),
- onChange: () => this.#handleInput(setFinalValue, false),
- }));
- this.setOptions(options);
- }
- #handleInput(setFn, skipUpdate) {
- const [valid, newV] = this.#from(this.domElement.value);
- if (valid) {
- this.#skipUpdate = skipUpdate;
- setFn(newV);
- }
- this.domElement.style.color = valid ? '' : 'var(--invalid-color)';
-
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.domElement.value = this.#to(v);
- this.domElement.style.color = '';
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {
- converters: {to, from},
- } = this.#options;
- this.#to = to;
- this.#from = from;
- return this;
- }
-}
-
-class Text extends ValueController {
- constructor(object, property) {
- super(object, property, 'muigui-checkbox');
- this.add(new TextView(this));
- this.updateDisplay();
- }
-}
-
-// const isConversion = o => typeof o.to === 'function' && typeof o.from === 'function';
-
-/**
- * possible inputs
- * add(o, p, min: number, max: number)
- * add(o, p, min: number, max: number, step: number)
- * add(o, p, array: [value])
- * add(o, p, array: [[key, value]])
- *
- * @param {*} object
- * @param {string} property
- * @param {...any} args
- * @returns {Controller}
- */
-function createController(object, property, ...args) {
- const [arg1] = args;
- if (Array.isArray(arg1)) {
- return new Select(object, property, {keyValues: arg1});
- }
-
- const t = typeof object[property];
- switch (t) {
- case 'number':
- if (typeof args[0] === 'number' && typeof args[1] === 'number') {
- const min = args[0];
- const max = args[1];
- const step = args[2];
- return new Range(object, property, {min, max, ...(step && {step})});
- }
- return args.length === 0
- ? new TextNumber(object, property, ...args)
- : new Range(object, property, ...args);
- case 'boolean':
- return new Checkbox(object, property, ...args);
- case 'function':
- return new Button(object, property, ...args);
- case 'string':
- return new Text(object, property, ...args);
- case 'undefined':
- throw new Error(`no property named ${property}`);
- default:
- throw new Error(`unhandled type ${t} for property ${property}`);
- }
-}
-
-const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
-const lerp = (a, b, t) => a + (b - a) * t;
-const fract = v => v >= 0 ? v % 1 : 1 - (v % 1);
-
-const f0 = v => +v.toFixed(0); // converts to string (eg 1.2 => "1"), then converts back to number (eg, "1.200" => 1.2)
-const f3 = v => +v.toFixed(3); // converts to string (eg 1.2 => "1.200"), then converts back to number (eg, "1.200" => 1.2)
-
-const hexToUint32RGB = v => (parseInt(v.substring(1, 3), 16) << 16) |
- (parseInt(v.substring(3, 5), 16) << 8 ) |
- (parseInt(v.substring(5, 7), 16) );
-const uint32RGBToHex = v => `#${(Math.round(v)).toString(16).padStart(6, '0')}`;
-const hexToUint32RGBA = v => (parseInt(v.substring(1, 3), 16) * 2 ** 24) +
- (parseInt(v.substring(3, 5), 16) * 2 ** 16) +
- (parseInt(v.substring(5, 7), 16) * 2 ** 8) +
- (parseInt(v.substring(7, 9), 16) );
-const uint32RGBAToHex = v => `#${(Math.round(v)).toString(16).padStart(8, '0')}`;
-
-const hexToUint8RGB = v => [
- parseInt(v.substring(1, 3), 16),
- parseInt(v.substring(3, 5), 16),
- parseInt(v.substring(5, 7), 16),
-];
-const uint8RGBToHex = v => `#${Array.from(v).map(v => v.toString(16).padStart(2, '0')).join('')}`;
-
-const hexToUint8RGBA = v => [
- parseInt(v.substring(1, 3), 16),
- parseInt(v.substring(3, 5), 16),
- parseInt(v.substring(5, 7), 16),
- parseInt(v.substring(7, 9), 16),
-];
-const uint8RGBAToHex = v => `#${Array.from(v).map(v => v.toString(16).padStart(2, '0')).join('')}`;
-
-const hexToFloatRGB = v => hexToUint8RGB(v).map(v => f3(v / 255));
-const floatRGBToHex = v => uint8RGBToHex(Array.from(v).map(v => Math.round(clamp(v * 255, 0, 255))));
-
-const hexToFloatRGBA = v => hexToUint8RGBA(v).map(v => f3(v / 255));
-const floatRGBAToHex = v => uint8RGBAToHex(Array.from(v).map(v => Math.round(clamp(v * 255, 0, 255))));
-
-const scaleAndClamp = v => clamp(Math.round(v * 255), 0, 255).toString(16).padStart(2, '0');
-
-const hexToObjectRGB = v => ({
- r: parseInt(v.substring(1, 3), 16) / 255,
- g: parseInt(v.substring(3, 5), 16) / 255,
- b: parseInt(v.substring(5, 7), 16) / 255,
-});
-const objectRGBToHex = v => `#${scaleAndClamp(v.r)}${scaleAndClamp(v.g)}${scaleAndClamp(v.b)}`;
-const hexToObjectRGBA = v => ({
- r: parseInt(v.substring(1, 3), 16) / 255,
- g: parseInt(v.substring(3, 5), 16) / 255,
- b: parseInt(v.substring(5, 7), 16) / 255,
- a: parseInt(v.substring(7, 9), 16) / 255,
-});
-const objectRGBAToHex = v => `#${scaleAndClamp(v.r)}${scaleAndClamp(v.g)}${scaleAndClamp(v.b)}${scaleAndClamp(v.a)}`;
-
-const hexToCssRGB = v => `rgb(${hexToUint8RGB(v).join(', ')})`;
-const cssRGBRegex = /^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/;
-const cssRGBToHex = v => {
- const m = cssRGBRegex.exec(v);
- return uint8RGBToHex([m[1], m[2], m[3]].map(v => parseInt(v)));
-};
-const hexToCssRGBA = v => `rgba(${hexToUint8RGBA(v).map((v, i) => i === 3 ? v / 255 : v).join(', ')})`;
-const cssRGBARegex = /^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+\.\d+|\d+)\s*\)\s*$/;
-const cssRGBAToHex = v => {
- const m = cssRGBARegex.exec(v);
- return uint8RGBAToHex([m[1], m[2], m[3], m[4]].map((v, i) => i === 3 ? (parseFloat(v) * 255 | 0) : parseInt(v)));
-};
-
-const hexToCssHSL = v => {
- const hsl = rgbUint8ToHsl(hexToUint8RGB(v)).map(v => f0(v));
- return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
-};
-const hexToCssHSLA = v => {
- const hsla = rgbaUint8ToHsla(hexToUint8RGBA(v)).map((v, i) => i === 3 ? f3(v) : f0(v));
- return `hsl(${hsla[0]} ${hsla[1]}% ${hsla[2]}% / ${hsla[3]})`;
-};
-const cssHSLRegex = /^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\)\s*$/;
-const cssHSLARegex = /^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\/\s*(\d+\.\d+|\d+)\s*\)\s*$/;
-
-const hex3DigitTo6Digit = v => `${v[0]}${v[0]}${v[1]}${v[1]}${v[2]}${v[2]}`;
-const cssHSLToHex = v => {
- const m = cssHSLRegex.exec(v);
- const rgb = hslToRgbUint8([m[1], m[2], m[3]].map(v => parseFloat(v)));
- return uint8RGBToHex(rgb);
-};
-const cssHSLAToHex = v => {
- const m = cssHSLARegex.exec(v);
- const rgba = hslaToRgbaUint8([m[1], m[2], m[3], m[4]].map(v => parseFloat(v)));
- return uint8RGBAToHex(rgba);
-};
-
-const euclideanModulo = (v, n) => ((v % n) + n) % n;
-
-function hslToRgbUint8([h, s, l]) {
- h = euclideanModulo(h, 360);
- s = clamp(s / 100, 0, 1);
- l = clamp(l / 100, 0, 1);
-
- const a = s * Math.min(l, 1 - l);
-
- function f(n) {
- const k = (n + h / 30) % 12;
- return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
- }
-
- return [f(0), f(8), f(4)].map(v => Math.round(v * 255));
-}
-
-function hslaToRgbaUint8([h, s, l, a]) {
- const rgb = hslToRgbUint8([h, s, l]);
- return [...rgb, a * 255 | 0];
-}
-
-function rgbFloatToHsl01([r, g, b]) {
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- const l = (min + max) * 0.5;
- const d = max - min;
- let h = 0;
- let s = 0;
-
- if (d !== 0) {
- s = (l === 0 || l === 1)
- ? 0
- : (max - l) / Math.min(l, 1 - l);
-
- switch (max) {
- case r: h = (g - b) / d + (g < b ? 6 : 0); break;
- case g: h = (b - r) / d + 2; break;
- case b: h = (r - g) / d + 4;
- }
- }
-
- return [h / 6, s, l];
-}
-
-function rgbaFloatToHsla01([r, g, b, a]) {
- const hsl = rgbFloatToHsl01([r, g, b]);
- return [...hsl, a];
-}
-
-const rgbUint8ToHsl = (rgb) => {
- const [h, s, l] = rgbFloatToHsl01(rgb.map(v => v / 255));
- return [h * 360, s * 100, l * 100];
-};
-
-const rgbaUint8ToHsla = (rgba) => {
- const [h, s, l, a] = rgbaFloatToHsla01(rgba.map(v => v / 255));
- return [h * 360, s * 100, l * 100, a];
-};
-
-function hsv01ToRGBFloat([hue, sat, val]) {
- sat = clamp(sat, 0, 1);
- val = clamp(val, 0, 1);
- return [hue, hue + 2 / 3, hue + 1 / 3].map(
- v => lerp(1, clamp(Math.abs(fract(v) * 6 - 3.0) - 1, 0, 1), sat) * val
- );
-}
-
-function hsva01ToRGBAFloat([hue, sat, val, alpha]) {
- const rgb = hsv01ToRGBFloat([hue, sat, val]);
- return [...rgb, alpha];
-}
-
-const round3 = v => Math.round(v * 1000) / 1000;
-
-function rgbFloatToHSV01([r, g, b]) {
- const p = b > g
- ? [b, g, -1, 2 / 3]
- : [g, b, 0, -1 / 3];
- const q = p[0] > r
- ? [p[0], p[1], p[3], r]
- : [r, p[1], p[2], p[0]];
- const d = q[0] - Math.min(q[3], q[1]);
- return [
- Math.abs(q[2] + (q[3] - q[1]) / (6 * d + Number.EPSILON)),
- d / (q[0] + Number.EPSILON),
- q[0],
- ].map(round3);
-}
-
-function rgbaFloatToHSVA01([r, g, b, a]) {
- const hsv = rgbFloatToHSV01([r, g, b]);
- return [...hsv, a];
-}
-
-// window.hsv01ToRGBFloat = hsv01ToRGBFloat;
-// window.rgbFloatToHSV01 = rgbFloatToHSV01;
-
-// Yea, meh!
-const hasAlpha = format => format.endsWith('a') || format.startsWith('hex8');
-
-const cssStringFormats = [
- { re: /^#(?:[0-9a-f]){6}$/i, format: 'hex6' },
- { re: /^(?:[0-9a-f]){6}$/i, format: 'hex6-no-hash' },
- { re: /^#(?:[0-9a-f]){8}$/i, format: 'hex8' },
- { re: /^(?:[0-9a-f]){8}$/i, format: 'hex8-no-hash' },
- { re: /^#(?:[0-9a-f]){3}$/i, format: 'hex3' },
- { re: /^(?:[0-9a-f]){3}$/i, format: 'hex3-no-hash' },
- { re: cssRGBRegex, format: 'css-rgb' },
- { re: cssHSLRegex, format: 'css-hsl' },
- { re: cssRGBARegex, format: 'css-rgba' },
- { re: cssHSLARegex, format: 'css-hsla' },
-];
-
-function guessStringColorFormat(v) {
- for (const formatInfo of cssStringFormats) {
- if (formatInfo.re.test(v)) {
- return formatInfo;
- }
- }
- return undefined;
-}
-
-function guessFormat(v) {
- switch (typeof v) {
- case 'number':
- console.warn('can not reliably guess format based on a number. You should pass in a format like {format: "uint32-rgb"} or {format: "uint32-rgb"}');
- return v <= 0xFFFFFF ? 'uint32-rgb' : 'uint32-rgba';
- case 'string': {
- const formatInfo = guessStringColorFormat(v.trim());
- if (formatInfo) {
- return formatInfo.format;
- }
- break;
- }
- case 'object':
- if (v instanceof Uint8Array || v instanceof Uint8ClampedArray) {
- if (v.length === 3) {
- return 'uint8-rgb';
- } else if (v.length === 4) {
- return 'uint8-rgba';
- }
- } else if (v instanceof Float32Array) {
- if (v.length === 3) {
- return 'float-rgb';
- } else if (v.length === 4) {
- return 'float-rgba';
- }
- } else if (Array.isArray(v)) {
- if (v.length === 3) {
- return 'float-rgb';
- } else if (v.length === 4) {
- return 'float-rgba';
- }
- } else {
- if ('r' in v && 'g' in v && 'b' in v) {
- if ('a' in v) {
- return 'object-rgba';
- } else {
- return 'object-rgb';
- }
- }
- }
- }
- throw new Error(`unknown color format: ${v}`);
-}
-
-function fixHex6(v) {
- return v.trim(v);
- //const formatInfo = guessStringColorFormat(v.trim());
- //const fix = formatInfo ? formatInfo.fix : v => v;
- //return fix(v.trim());
-}
-
-function fixHex8(v) {
- return v.trim(v);
- //const formatInfo = guessStringColorFormat(v.trim());
- //const fix = formatInfo ? formatInfo.fix : v => v;
- //return fix(v.trim());
-}
-
-function hex6ToHex3(hex6) {
- return (hex6[1] === hex6[2] &&
- hex6[3] === hex6[4] &&
- hex6[5] === hex6[6])
- ? `#${hex6[1]}${hex6[3]}${hex6[5]}`
- : hex6;
-}
-
-const hex3RE = /^(#|)([0-9a-f]{3})$/i;
-function hex3ToHex6(hex3) {
- const m = hex3RE.exec(hex3);
- if (m) {
- const [, , m2] = m;
- return `#${hex3DigitTo6Digit(m2)}`;
- }
- return hex3;
-}
-
-function fixHex3(v) {
- return hex6ToHex3(fixHex6(v));
-}
-
-const strToRGBObject = (s) => {
- try {
- const json = s.replace(/([a-z])/g, '"$1"');
- const rgb = JSON.parse(json);
- if (Number.isNaN(rgb.r) || Number.isNaN(rgb.g) || Number.isNaN(rgb.b)) {
- throw new Error('not {r, g, b}');
- }
- return [true, rgb];
- } catch (e) {
- return [false];
- }
-};
-
-const strToRGBAObject = (s) => {
- try {
- const json = s.replace(/([a-z])/g, '"$1"');
- const rgba = JSON.parse(json);
- if (Number.isNaN(rgba.r) || Number.isNaN(rgba.g) || Number.isNaN(rgba.b) || Number.isNaN(rgba.a)) {
- throw new Error('not {r, g, b, a}');
- }
- return [true, rgba];
- } catch (e) {
- return [false];
- }
-};
-
-const strToCssRGB = s => {
- const m = cssRGBRegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, `rgb(${v.join(', ')})`];
-};
-
-const strToCssRGBA = s => {
- const m = cssRGBARegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map((v, i) => i === 3 ? parseFloat(v) : parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, `rgba(${v.join(', ')})`];
-};
-
-const strToCssHSL = s => {
- const m = cssHSLRegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseFloat(v));
- const outOfRange = v.find(v => Number.isNaN(v));
- return [!outOfRange, `hsl(${v[0]}, ${v[1]}%, ${v[2]}%)`];
-};
-
-const strToCssHSLA = s => {
- const m = cssHSLARegex.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map(v => parseFloat(v));
- const outOfRange = v.find(v => Number.isNaN(v));
- return [!outOfRange, `hsl(${v[0]} ${v[1]}% ${v[2]}% / ${v[3]})`];
-};
-
-const rgbObjectToStr = rgb => {
- return `{r:${f3(rgb.r)}, g:${f3(rgb.g)}, b:${f3(rgb.b)}}`;
-};
-const rgbaObjectToStr = rgba => {
- return `{r:${f3(rgba.r)}, g:${f3(rgba.g)}, b:${f3(rgba.b)}}, a:${f3(rgba.a)}}`;
-};
-
-const strTo3IntsRE = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/;
-const strTo3Ints = s => {
- const m = strTo3IntsRE.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, v];
-};
-
-const strTo4IntsRE = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/;
-const strTo4Ints = s => {
- const m = strTo4IntsRE.exec(s);
- if (!m) {
- return [false];
- }
- const v = [m[1], m[2], m[3], m[4]].map(v => parseInt(v));
- const outOfRange = v.find(v => v > 255);
- return [!outOfRange, v];
-};
-
-const strTo3Floats = s => {
- const numbers = s.split(',').map(s => s.trim());
- const v = numbers.map(v => parseFloat(v));
- if (v.length !== 3) {
- return [false];
- }
- // Note: using isNaN not Number.isNaN
- const badNdx = numbers.findIndex(v => isNaN(v));
- return [badNdx < 0, v.map(v => f3(v))];
-};
-
-const strTo4Floats = s => {
- const numbers = s.split(',').map(s => s.trim());
- const v = numbers.map(v => parseFloat(v));
- if (v.length !== 4) {
- return [false];
- }
- // Note: using isNaN not Number.isNaN
- const badNdx = numbers.findIndex(v => isNaN(v));
- return [badNdx < 0, v.map(v => f3(v))];
-};
-
-const strToUint32RGBRegex = /^\s*(?:0x){0,1}([0-9a-z]{1,6})\s*$/i;
-const strToUint32RGB = s => {
- const m = strToUint32RGBRegex.exec(s);
- if (!m) {
- return [false];
- }
- return [true, parseInt(m[1], 16)];
-};
-
-const strToUint32RGBARegex = /^\s*(?:0x){0,1}([0-9a-z]{1,8})\s*$/i;
-const strToUint32RGBA = s => {
- const m = strToUint32RGBARegex.exec(s);
- if (!m) {
- return [false];
- }
- return [true, parseInt(m[1], 16)];
-};
-
-const hex6RE = /^\s*#[a-f0-9]{6}\s*$|^\s*#[a-f0-9]{3}\s*$/i;
-const hexNoHash6RE = /^\s*[a-f0-9]{6}\s*$/i;
-const hex8RE = /^\s*#[a-f0-9]{8}\s*$/i;
-const hexNoHash8RE = /^\s*[a-f0-9]{8}\s*$/i;
-
-// For each format converter
-//
-// fromHex/toHex convert from/to '#RRGGBB'
-//
-// fromHex converts from the string '#RRBBGG' to the format
-// (eg: for uint32-rgb, '#123456' becomes 0x123456)
-//
-// toHex converts from the format to '#RRGGBB'
-// (eg: for uint8-rgb, [16, 33, 50] becomes '#102132')
-//
-//
-// fromStr/toStr convert from/to what's in the input[type=text] element
-//
-// toStr converts from the format to its string representation
-// (eg, for object-rgb, {r: 1, g: 0.5, b:0} becomes "{r: 1, g: 0.5, b:0}")
-// ^object ^string
-//
-// fromStr converts its string representation to its format
-// (eg, for object-rgb) "{r: 1, g: 0.5, b:0}" becomes {r: 1, g: 0.5, b:0})
-// ^string ^object
-// fromString returns an array which is [valid, v]
-// where valid is true if the string was a valid and v is the converted
-// format if v is true.
-//
-// Note: toStr should convert to "ideal" form (whatever that is).
-// (eg, for css-rgb
-// "{ r: 0.10000, g: 001, b: 0}" becomes "{r: 0.1, g: 1, b: 0}"
-// notice that css-rgb is a string to a string
-// )
-const colorFormatConverters = {
- 'hex6': {
- color: {
- from: v => [true, v],
- to: fixHex6,
- },
- text: {
- from: v => [hex6RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex8': {
- color: {
- from: v => [true, v],
- to: fixHex8,
- },
- text: {
- from: v => [hex8RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex3': {
- color: {
- from: v => [true, fixHex3(v)],
- to: hex3ToHex6,
- },
- text: {
- from: v => [hex6RE.test(v), hex6ToHex3(v.trim())],
- to: v => v,
- },
- },
- 'hex6-no-hash': {
- color: {
- from: v => [true, v.substring(1)],
- to: v => `#${fixHex6(v)}`,
- },
- text: {
- from: v => [hexNoHash6RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex8-no-hash': {
- color: {
- from: v => [true, v.substring(1)],
- to: v => `#${fixHex8(v)}`,
- },
- text: {
- from: v => [hexNoHash8RE.test(v), v.trim()],
- to: v => v,
- },
- },
- 'hex3-no-hash': {
- color: {
- from: v => [true, fixHex3(v).substring(1)],
- to: hex3ToHex6,
- },
- text: {
- from: v => [hexNoHash6RE.test(v), hex6ToHex3(v.trim())],
- to: v => v,
- },
- },
- 'uint32-rgb': {
- color: {
- from: v => [true, hexToUint32RGB(v)],
- to: uint32RGBToHex,
- },
- text: {
- from: v => strToUint32RGB(v),
- to: v => `0x${v.toString(16).padStart(6, '0')}`,
- },
- },
- 'uint32-rgba': {
- color: {
- from: v => [true, hexToUint32RGBA(v)],
- to: uint32RGBAToHex,
- },
- text: {
- from: v => strToUint32RGBA(v),
- to: v => `0x${v.toString(16).padStart(8, '0')}`,
- },
- },
- 'uint8-rgb': {
- color: {
- from: v => [true, hexToUint8RGB(v)],
- to: uint8RGBToHex,
- },
- text: {
- from: strTo3Ints,
- to: v => v.join(', '),
- },
- },
- 'uint8-rgba': {
- color: {
- from: v => [true, hexToUint8RGBA(v)],
- to: uint8RGBAToHex,
- },
- text: {
- from: strTo4Ints,
- to: v => v.join(', '),
- },
- },
- 'float-rgb': {
- color: {
- from: v => [true, hexToFloatRGB(v)],
- to: floatRGBToHex,
- },
- text: {
- from: strTo3Floats,
- // need Array.from because map of Float32Array makes a Float32Array
- to: v => Array.from(v).map(v => f3(v)).join(', '),
- },
- },
- 'float-rgba': {
- color: {
- from: v => [true, hexToFloatRGBA(v)],
- to: floatRGBAToHex,
- },
- text: {
- from: strTo4Floats,
- // need Array.from because map of Float32Array makes a Float32Array
- to: v => Array.from(v).map(v => f3(v)).join(', '),
- },
- },
- 'object-rgb': {
- color: {
- from: v => [true, hexToObjectRGB(v)],
- to: objectRGBToHex,
- },
- text: {
- from: strToRGBObject,
- to: rgbObjectToStr,
- },
- },
- 'object-rgba': {
- color: {
- from: v => [true, hexToObjectRGBA(v)],
- to: objectRGBAToHex,
- },
- text: {
- from: strToRGBAObject,
- to: rgbaObjectToStr,
- },
- },
- 'css-rgb': {
- color: {
- from: v => [true, hexToCssRGB(v)],
- to: cssRGBToHex,
- },
- text: {
- from: strToCssRGB,
- to: v => strToCssRGB(v)[1],
- },
- },
- 'css-rgba': {
- color: {
- from: v => [true, hexToCssRGBA(v)],
- to: cssRGBAToHex,
- },
- text: {
- from: strToCssRGBA,
- to: v => strToCssRGBA(v)[1],
- },
- },
- 'css-hsl': {
- color: {
- from: v => [true, hexToCssHSL(v)],
- to: cssHSLToHex,
- },
- text: {
- from: strToCssHSL,
- to: v => strToCssHSL(v)[1],
- },
- },
- 'css-hsla': {
- color: {
- from: v => [true, hexToCssHSLA(v)],
- to: cssHSLAToHex,
- },
- text: {
- from: strToCssHSLA,
- to: v => strToCssHSLA(v)[1],
- },
- },
-};
-
-class ElementView extends View {
- constructor(tag, className) {
- super(createElem(tag, {className}));
- }
-}
-
-// TODO: remove this? Should just be user side
-class Canvas extends LabelController {
- #canvasElem;
-
- constructor() {
- super('muigui-canvas');
- this.#canvasElem = this.add(
- new ElementView('canvas', 'muigui-canvas'),
- ).domElement;
- }
- get canvas() {
- return this.#canvasElem;
- }
-}
-
-class ColorView extends EditView {
- #to;
- #from;
- #colorElem;
- #skipUpdate;
- #options = {
- converters: identity,
- };
-
- constructor(setter, options) {
- const colorElem = createElem('input', {
- type: 'color',
- onInput: () => {
- const [valid, newV] = this.#from(colorElem.value);
- if (valid) {
- this.#skipUpdate = true;
- setter.setValue(newV);
- }
- },
- onChange: () => {
- const [valid, newV] = this.#from(colorElem.value);
- if (valid) {
- this.#skipUpdate = true;
- setter.setFinalValue(newV);
- }
- },
- });
- super(createElem('div', {}, [colorElem]));
- this.setOptions(options);
- this.#colorElem = colorElem;
- }
- updateDisplay(v) {
- if (!this.#skipUpdate) {
- this.#colorElem.value = this.#to(v);
- }
- this.#skipUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {converters: {to, from}} = this.#options;
- this.#to = to;
- this.#from = from;
- return this;
- }
-}
-
-class Color extends ValueController {
- #colorView;
- #textView;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-color');
- const format = options.format || guessFormat(this.getValue());
- const {color, text} = colorFormatConverters[format];
- this.#colorView = this.add(new ColorView(this, {converters: color}));
- this.#textView = this.add(new TextView(this, {converters: text}));
- this.updateDisplay();
- }
- setOptions(options) {
- const {format} = options;
- if (format) {
- const {color, text} = colorFormatConverters[format];
- this.#colorView.setOptions({converters: color});
- this.#textView.setOptions({converters: text});
- }
- super.setOptions(options);
- return this;
- }
-}
-
-// This feels like it should be something else like
-// gui.addController({className: 'muigui-divider')};
-class Divider extends Controller {
- constructor() {
- super('muigui-divider');
- }
-}
-
-class Container extends Controller {
- #controllers;
- #childDestController;
-
- constructor(className) {
- super(className);
- this.#controllers = [];
- this.#childDestController = this;
- }
- get children() {
- return this.#controllers; // should we return a copy?
- }
- get controllers() {
- return this.#controllers.filter(c => !(c instanceof Container));
- }
- get folders() {
- return this.#controllers.filter(c => c instanceof Container);
- }
- reset(recursive = true) {
- for (const controller of this.#controllers) {
- if (!(controller instanceof Container) || recursive) {
- controller.reset(recursive);
- }
- }
- return this;
- }
- updateDisplay() {
- for (const controller of this.#controllers) {
- controller.updateDisplay();
- }
- return this;
- }
- remove(controller) {
- const ndx = this.#controllers.indexOf(controller);
- if (ndx >= 0) {
- const c = this.#controllers.splice(ndx, 1);
- const c0 = c[0];
- const elem = c0.domElement;
- elem.remove();
- c0.setParent(null);
- }
- return this;
- }
- _addControllerImpl(controller) {
- this.domElement.appendChild(controller.domElement);
- this.#controllers.push(controller);
- controller.setParent(this);
- return controller;
- }
- addController(controller) {
- return this.#childDestController._addControllerImpl(controller);
- }
- pushContainer(container) {
- this.addController(container);
- this.#childDestController = container;
- return container;
- }
- popContainer() {
- this.#childDestController = this.#childDestController.parent;
- return this;
- }
-}
-
-class Folder extends Container {
- #labelElem;
-
- constructor(name = 'Controls', className = 'muigui-menu') {
- super(className);
- this.#labelElem = createElem('label');
- this.addElem(createElem('button', {
- type: 'button',
- onClick: () => this.toggleOpen(),
- }, [this.#labelElem]));
- this.pushContainer(new Container());
- this.name(name);
- this.open();
- }
- open(open = true) {
- this.domElement.classList.toggle('muigui-closed', !open);
- this.domElement.classList.toggle('muigui-open', open);
- return this;
- }
- close() {
- return this.open(false);
- }
- name(name) {
- this.#labelElem.textContent = name;
- return this;
- }
- title(title) {
- return this.name(title);
- }
- toggleOpen() {
- this.open(!this.domElement.classList.contains('muigui-open'));
- return this;
- }
-}
-
-// This feels like it should be something else like
-// gui.addDividing = new Controller()
-class Label extends Controller {
- constructor(text) {
- super('muigui-label');
- this.text(text);
- }
- text(text) {
- this.domElement.textContent = text;
- return this;
- }
-}
-
-function noop$1() {
-}
-
-function computeRelativePosition(elem, event, start) {
- const rect = elem.getBoundingClientRect();
- const x = event.clientX - rect.left;
- const y = event.clientY - rect.top;
- const nx = x / rect.width;
- const ny = y / rect.height;
- start = start || [x, y];
- const dx = x - start[0];
- const dy = y - start[1];
- const ndx = dx / rect.width;
- const ndy = dy / rect.width;
- return {x, y, nx, ny, dx, dy, ndx, ndy};
-}
-
-function addTouchEvents(elem, {onDown = noop$1, onMove = noop$1, onUp = noop$1}) {
- let start;
- const pointerMove = function(event) {
- const e = {
- type: 'move',
- ...computeRelativePosition(elem, event, start),
- };
- onMove(e);
- };
-
- const pointerUp = function(event) {
- elem.releasePointerCapture(event.pointerId);
- elem.removeEventListener('pointermove', pointerMove);
- elem.removeEventListener('pointerup', pointerUp);
-
- document.body.style.backgroundColor = '';
-
- onUp('up');
- };
-
- const pointerDown = function(event) {
- elem.addEventListener('pointermove', pointerMove);
- elem.addEventListener('pointerup', pointerUp);
- elem.setPointerCapture(event.pointerId);
-
- const rel = computeRelativePosition(elem, event);
- start = [rel.x, rel.y];
- onDown({
- type: 'down',
- ...rel,
- });
- };
-
- elem.addEventListener('pointerdown', pointerDown);
-
- return function() {
- elem.removeEventListener('pointerdown', pointerDown);
- };
-}
-
-const svg$3 = `
-
-
-
-`;
-
-function connectFillTargets(elem) {
- elem.querySelectorAll('[data-src]').forEach(srcElem => {
- const id = getNewId();
- srcElem.id = id;
- elem.querySelectorAll(`[data-target=${srcElem.dataset.src}]`).forEach(targetElem => {
- targetElem.setAttribute('fill', `url(#${id})`);
- });
- });
- return elem;
-}
-
-// Was originally going to make alpha an option. Issue is
-// hard coded conversions?
-class ColorChooserView extends EditView {
- #to;
- #from;
- #satLevelElem;
- #circleElem;
- #hueUIElem;
- #hueElem;
- #hueCursorElem;
- #alphaUIElem;
- #alphaElem;
- #alphaCursorElem;
- #hsva;
- #skipHueUpdate;
- #skipSatLevelUpdate;
- #skipAlphaUpdate;
- #options = {
- converters: identity,
- alpha: false,
- };
- #convertInternalToHex;
- #convertHexToInternal;
-
- constructor(setter, options) {
- super(createElem('div', {
- innerHTML: svg$3,
- className: 'muigui-no-scroll',
- }));
- this.#satLevelElem = this.domElement.children[0];
- this.#hueUIElem = this.domElement.children[1];
- this.#alphaUIElem = this.domElement.children[2];
- connectFillTargets(this.#satLevelElem);
- connectFillTargets(this.#hueUIElem);
- connectFillTargets(this.#alphaUIElem);
- this.#circleElem = this.$('.muigui-color-chooser-circle');
- this.#hueElem = this.$('[data-src=muigui-color-chooser-hue]');
- this.#hueCursorElem = this.$('.muigui-color-chooser-hue-cursor');
- this.#alphaElem = this.$('[data-src=muigui-color-chooser-alpha]');
- this.#alphaCursorElem = this.$('.muigui-color-chooser-alpha-cursor');
-
- const handleSatLevelChange = (e) => {
- const s = clamp$1(e.nx, 0, 1);
- const v = clamp$1(e.ny, 0, 1);
- this.#hsva[1] = s;
- this.#hsva[2] = (1 - v);
- this.#skipHueUpdate = true;
- this.#skipAlphaUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- const handleHueChange = (e) => {
- const h = clamp$1(e.nx, 0, 1);
- this.#hsva[0] = h;
- this.#skipSatLevelUpdate = true;
- this.#skipAlphaUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- const handleAlphaChange = (e) => {
- const a = clamp$1(e.nx, 0, 1);
- this.#hsva[3] = a;
- this.#skipHueUpdate = true;
- this.#skipSatLevelUpdate = true;
- const [valid, newV] = this.#from(this.#convertInternalToHex(this.#hsva));
- if (valid) {
- setter.setValue(newV);
- }
- };
-
- addTouchEvents(this.#satLevelElem, {
- onDown: handleSatLevelChange,
- onMove: handleSatLevelChange,
- });
- addTouchEvents(this.#hueUIElem, {
- onDown: handleHueChange,
- onMove: handleHueChange,
- });
- addTouchEvents(this.#alphaUIElem, {
- onDown: handleAlphaChange,
- onMove: handleAlphaChange,
- });
- this.setOptions(options);
- }
- updateDisplay(newV) {
- if (!this.#hsva) {
- this.#hsva = this.#convertHexToInternal(this.#to(newV));
- }
- {
- const [h, s, v, a = 1] = this.#convertHexToInternal(this.#to(newV));
- // Don't copy the hue if it was un-computable.
- if (!this.#skipHueUpdate) {
- this.#hsva[0] = s > 0.001 && v > 0.001 ? h : this.#hsva[0];
- }
- if (!this.#skipSatLevelUpdate) {
- this.#hsva[1] = s;
- this.#hsva[2] = v;
- }
- if (!this.#skipAlphaUpdate) {
- this.#hsva[3] = a;
- }
- }
- {
- const [h, s, v, a] = this.#hsva;
- const [hue, sat, lum] = rgbaFloatToHsla01(hsva01ToRGBAFloat(this.#hsva));
-
- if (!this.#skipHueUpdate) {
- this.#hueCursorElem.setAttribute('transform', `translate(${h * 64}, 0)`);
- }
- this.#hueElem.children[0].setAttribute('stop-color', `hsl(${hue * 360} 0% 100% / ${a})`);
- this.#hueElem.children[1].setAttribute('stop-color', `hsl(${hue * 360} 100% 50% / ${a})`);
- if (!this.#skipAlphaUpdate) {
- this.#alphaCursorElem.setAttribute('transform', `translate(${a * 64}, 0)`);
- }
- this.#alphaElem.children[0].setAttribute('stop-color', `hsl(${hue * 360} ${sat * 100}% ${lum * 100}% / 0)`);
- this.#alphaElem.children[1].setAttribute('stop-color', `hsl(${hue * 360} ${sat * 100}% ${lum * 100}% / 1)`);
-
- if (!this.#skipSatLevelUpdate) {
- this.#circleElem.setAttribute('cx', `${s * 64}`);
- this.#circleElem.setAttribute('cy', `${(1 - v) * 48}`);
- }
- }
- this.#skipHueUpdate = false;
- this.#skipSatLevelUpdate = false;
- this.#skipAlphaUpdate = false;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {converters: {to, from}, alpha} = this.#options;
- this.#alphaUIElem.style.display = alpha ? '' : 'none';
- this.#convertInternalToHex = alpha
- ? v => floatRGBAToHex(hsva01ToRGBAFloat(v))
- : v => floatRGBToHex(hsv01ToRGBFloat(v));
- this.#convertHexToInternal = alpha
- ? v => rgbaFloatToHSVA01(hexToFloatRGBA(v))
- : v => rgbFloatToHSV01(hexToFloatRGB(v));
- this.#to = to;
- this.#from = from;
- return this;
- }
-}
-
-/*
-
-holder = new TabHolder
-tab = holder.add(new Tab("name"))
-tab.add(...)
-
-
-pc = new PopdownController
-top = pc.add(new Row())
-top.add(new Button());
-values = topRow.add(new Div())
-bottom = pc.add(new Row());
-
-
-
-pc = new PopdownController
-pc.addTop
-pc.addTop
-
-pc.addBottom
-
-
-*/
-
-class PopDownController extends ValueController {
- #top;
- #valuesView;
- #checkboxElem;
- #bottom;
- #options = {
- open: false,
- };
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-pop-down-controller');
- /*
- [ValueView
- [[B][values]] upper row
- [[ visual ]] lower row
- ]
- */
- this.#top = this.add(new ElementView('div', 'muigui-pop-down-top'));
-// this.#top.add(new CheckboxView(makeSetter(this.#options, 'open')));
- const checkboxElem = this.#top.addElem(createElem('input', {
- type: 'checkbox',
- onChange: () => {
- this.#options.open = checkboxElem.checked;
- this.updateDisplay();
- },
- }));
- this.#checkboxElem = checkboxElem;
- this.#valuesView = this.#top.add(new ElementView('div', 'muigui-pop-down-values'));
- this.#bottom = this.add(new ElementView('div', 'muigui-pop-down-bottom'));
- this.setOptions(options);
- }
- setKnobColor(bgCssColor/*, fgCssColor*/) {
- if (this.#checkboxElem) {
- this.#checkboxElem.style = `
- --range-color: ${bgCssColor};
- --value-bg-color: ${bgCssColor};
- `;
- }
- }
- updateDisplay() {
- super.updateDisplay();
- const {open} = this.#options;
- this.domElement.children[1].classList.toggle('muigui-open', open);
- this.domElement.children[1].classList.toggle('muigui-closed', !open);
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- super.setOptions(options);
- this.updateDisplay();
- }
- addTop(view) {
- return this.#valuesView.add(view);
- }
- addBottom(view) {
- return this.#bottom.add(view);
- }
-}
-
-class ColorChooser extends PopDownController {
- #colorView;
- #textView;
- #to;
-
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-color-chooser');
- const format = options.format || guessFormat(this.getValue());
- const {color, text} = colorFormatConverters[format];
- this.#to = color.to;
- this.#textView = new TextView(this, {converters: text, alpha: hasAlpha(format)});
- this.#colorView = new ColorChooserView(this, {converters: color, alpha: hasAlpha(format)});
- this.addTop(this.#textView);
- this.addBottom(this.#colorView);
- // WTF! FIX!
- this.__setKnobHelper = () => {
- if (this.#to) {
- const hex6Or8 = this.#to(this.getValue());
- const hsl = rgbUint8ToHsl(hexToUint8RGB(hex6Or8));
- hsl[2] = (hsl[2] + 50) % 100;
- const hex = uint8RGBToHex(hslToRgbUint8(hsl));
- this.setKnobColor(`${hex6Or8.substring(0, 7)}FF`, hex);
- }
- };
- this.updateDisplay();
- }
- updateDisplay() {
- super.updateDisplay();
- if (this.__setKnobHelper) {
- this.__setKnobHelper();
- }
- }
- setOptions(options) {
- super.setOptions(options);
- return this;
- }
-}
-
-function showCSS(ob) {
- if (ob.prototype.css) {
- showCSS(ob.prototype);
- }
-}
-
-class Layout extends View {
- static css = 'bar';
- constructor(tag, className) {
- super(createElem(tag, {className}));
-
- showCSS(this);
- }
-}
-
-/*
-class ValueController ?? {
- const row = this.add(new Row());
- const label = row.add(new Label());
- const div = row.add(new Div());
- const row = div.add(new Row());
-}
-*/
-
-/*
-class MyCustomThing extends ValueController {
- constructor(object, property, options) {
- const topRow = this.add(new Row());
- const bottomRow = this.add(new Row());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- topRow.add(new NumberView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- bottomRow.add(new DirectionView());
- }
-}
- new Grid([
- [new
- ]
- */
-
-class Column extends Layout {
- constructor() {
- super('div', 'muigui-row');
- }
-}
-
-class Frame extends Layout {
- static css = 'foo';
- constructor() {
- super('div', 'muigui-frame');
- }
- static get foo() {
- return 'boo';
- }
-}
-
-class Grid extends Layout {
- constructor() {
- super('div', 'muigui-grid');
- }
-}
-
-class Row extends Layout {
- constructor() {
- super('div', 'muigui-row');
- }
-}
-
-class GUIFolder extends Folder {
- add(object, property, ...args) {
- const controller = object instanceof Controller
- ? object
- : createController(object, property, ...args);
- return this.addController(controller);
- }
- addCanvas(name) {
- return this.addController(new Canvas(name));
- }
- addColor(object, property, options = {}) {
- const value = object[property];
- if (hasAlpha(options.format || guessFormat(value))) {
- return this.addController(new ColorChooser(object, property, options));
- } else {
- return this.addController(new Color(object, property, options));
- }
- }
- addDivider() {
- return this.addController(new Divider());
- }
- addFolder(name) {
- return this.addController(new GUIFolder(name));
- }
- addLabel(text) {
- return this.addController(new Label(text));
- }
-}
-
-class MuiguiElement extends HTMLElement {
- constructor() {
- super();
- this.shadow = this.attachShadow({mode: 'open'});
- }
-}
-
-customElements.define('muigui-element', MuiguiElement);
-
-const baseStyleSheet = new CSSStyleSheet();
-baseStyleSheet.replaceSync(css.default);
-const userStyleSheet = new CSSStyleSheet();
-
-function makeStyleSheetUpdater(styleSheet) {
- let newCss;
- let newCssPromise;
-
- function updateStyle() {
- if (newCss && !newCssPromise) {
- const s = newCss;
- newCss = undefined;
- newCssPromise = styleSheet.replace(s).then(() => {
- newCssPromise = undefined;
- updateStyle();
- });
- }
- }
-
- return function updateStyleSheet(css) {
- newCss = css;
- updateStyle();
- };
-}
-
-const updateBaseStyle = makeStyleSheetUpdater(baseStyleSheet);
-const updateUserStyle = makeStyleSheetUpdater(userStyleSheet);
-
-class GUI extends GUIFolder {
- static converters = converters;
- static mapRange = mapRange;
- static makeRangeConverters = makeRangeConverters;
- static makeRangeOptions = makeRangeOptions;
- static makeMinMaxPair = makeMinMaxPair;
- #localStyleSheet = new CSSStyleSheet();
-
- constructor(options = {}) {
- super('Controls', 'muigui-root');
- if (options instanceof HTMLElement) {
- options = {parent: options};
- }
- const {
- autoPlace = true,
- width,
- title = 'Controls',
- } = options;
- let {
- parent,
- } = options;
-
- if (width) {
- this.domElement.style.width = /^\d+$/.test(width) ? `${width}px` : width;
- }
- if (parent === undefined && autoPlace) {
- parent = document.body;
- this.domElement.classList.add('muigui-auto-place');
- }
- if (parent) {
- const muiguiElement = createElem('muigui-element');
- muiguiElement.shadowRoot.adoptedStyleSheets = [baseStyleSheet, userStyleSheet, this.#localStyleSheet];
- muiguiElement.shadow.appendChild(this.domElement);
- parent.appendChild(muiguiElement);
- }
- if (title) {
- this.title(title);
- }
- this.domElement.classList.add('muigui', 'muigui-colors');
- }
- setStyle(css) {
- this.#localStyleSheet.replace(css);
- }
- static setBaseStyles(css) {
- updateBaseStyle(css);
- }
- static getBaseStyleSheet() {
- return baseStyleSheet;
- }
- static setUserStyles(css) {
- updateUserStyle(css);
- }
- static getUserStyleSheet() {
- return userStyleSheet;
- }
- static setTheme(name) {
- GUI.setBaseStyles(`${css.default}\n${css.themes[name] || ''}`);
- }
-}
-
-function noop() {
-}
-
-const keyDirections = {
- ArrowLeft: [-1, 0],
- ArrowRight: [1, 0],
- ArrowUp: [0, -1],
- ArrowDown: [0, 1],
-};
-
-// This probably needs to be global
-function addKeyboardEvents(elem, {onDown = noop, onUp = noop}) {
- const keyDown = function(event) {
- const mult = event.shiftKey ? 10 : 1;
- const [dx, dy] = (keyDirections[event.key] || [0, 0]).map(v => v * mult);
- const fn = event.type === 'keydown' ? onDown : onUp;
- fn({
- type: event.type.substring(3),
- dx,
- dy,
- event,
- });
- };
-
- elem.addEventListener('keydown', keyDown);
- elem.addEventListener('keyup', keyDown);
-
- return function() {
- elem.removeEventListener('keydown', keyDown);
- elem.removeEventListener('keyup', keyDown);
- };
-}
-
-function assert(truthy, msg = '') {
- if (!truthy) {
- throw new Error(msg);
- }
-}
-
-function getEllipsePointForAngle(cx, cy, rx, ry, phi, theta) {
- const m = Math.abs(rx) * Math.cos(theta);
- const n = Math.abs(ry) * Math.sin(theta);
-
- return [
- cx + Math.cos(phi) * m - Math.sin(phi) * n,
- cy + Math.sin(phi) * m + Math.cos(phi) * n,
- ];
-}
-
-function getEndpointParameters(cx, cy, rx, ry, phi, theta, dTheta) {
- const [x1, y1] = getEllipsePointForAngle(cx, cy, rx, ry, phi, theta);
- const [x2, y2] = getEllipsePointForAngle(cx, cy, rx, ry, phi, theta + dTheta);
-
- const fa = Math.abs(dTheta) > Math.PI ? 1 : 0;
- const fs = dTheta > 0 ? 1 : 0;
-
- return { x1, y1, x2, y2, fa, fs };
-}
-
-function arc(cx, cy, r, start, end) {
- assert(Math.abs(start - end) <= Math.PI * 2);
- assert(start >= -Math.PI && start <= Math.PI * 2);
- assert(start <= end);
- assert(end >= -Math.PI && end <= Math.PI * 4);
-
- const { x1, y1, x2, y2, fa, fs } = getEndpointParameters(cx, cy, r, r, 0, start, end - start);
- return Math.abs(Math.abs(start - end) - Math.PI * 2) > Number.EPSILON
- ? `M${cx} ${cy} L${x1} ${y1} A ${r} ${r} 0 ${fa} ${fs} ${x2} ${y2} L${cx} ${cy}`
- : `M${x1} ${y1} L${x1} ${y1} A ${r} ${r} 0 ${fa} ${fs} ${x2} ${y2}`;
-}
-
-const svg$2 = `
-
-`;
-
-const twoPiMod = v => euclideanModulo$1(v + Math.PI, Math.PI * 2) - Math.PI;
-
-class DirectionView extends EditView {
- #arrowElem;
- #rangeElem;
- #lastV;
- #wrap;
- #options = {
- step: 1,
- min: -180,
- max: 180,
-
- /*
- --------
- / -ฯ/2 \
- / | \
- |<- -ฯ * |
- | * 0 ->| zero is down the positive X axis
- |<- +ฯ * |
- \ | /
- \ ฯ/2 /
- --------
- */
- dirMin: -Math.PI,
- dirMax: Math.PI,
- //dirMin: Math.PI * 0.5,
- //dirMax: Math.PI * 2.5,
- //dirMin: -Math.PI * 0.75, // test 10:30 to 7:30
- //dirMax: Math.PI * 0.75,
- //dirMin: Math.PI * 0.75, // test 7:30 to 10:30
- //dirMax: -Math.PI * 0.75,
- //dirMin: -Math.PI * 0.75, // test 10:30 to 1:30
- //dirMax: -Math.PI * 0.25,
- //dirMin: Math.PI * 0.25, // test 4:30 to 7:30
- //dirMax: Math.PI * 0.75,
- //dirMin: Math.PI * 0.75, // test 4:30 to 7:30
- //dirMax: Math.PI * 0.25,
- wrap: undefined,
- converters: identity,
- };
-
- constructor(setter, options = {}) {
- const wheelHelper = createWheelHelper();
- super(createElem('div', {
- className: 'muigui-direction muigui-no-scroll',
- innerHTML: svg$2,
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- let tempV = this.#lastV + delta;
- if (this.#wrap) {
- tempV = euclideanModulo$1(tempV - min, max - min) + min;
- }
- const newV = clamp$1(stepify(tempV, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- const handleTouch = (e) => {
- const {min, max, step, dirMin, dirMax} = this.#options;
- const nx = e.nx * 2 - 1;
- const ny = e.ny * 2 - 1;
- const a = Math.atan2(ny, nx);
-
- const center = (dirMin + dirMax) / 2;
-
- const centeredAngle = twoPiMod(a - center);
- const centeredStart = twoPiMod(dirMin - center);
- const diff = dirMax - dirMin;
-
- const n = clamp$1((centeredAngle - centeredStart) / (diff), 0, 1);
- const newV = stepify(min + (max - min) * n, v => v, step);
- setter.setValue(newV);
- };
- addTouchEvents(this.domElement, {
- onDown: handleTouch,
- onMove: handleTouch,
- });
- addKeyboardEvents(this.domElement, {
- onDown: (e) => {
- const {min, max, step} = this.#options;
- const newV = clamp$1(stepify(this.#lastV + e.dx * step, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- this.#arrowElem = this.$('#muigui-arrow');
- this.#rangeElem = this.$('#muigui-range');
- this.setOptions(options);
- }
- updateDisplay(v) {
- this.#lastV = v;
- const {min, max} = this.#options;
- const n = (v - min) / (max - min);
- const angle = lerp$1(this.#options.dirMin, this.#options.dirMax, n);
- this.#arrowElem.style.transform = `rotate(${angle}rad)`;
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- const {dirMin, dirMax, wrap} = this.#options;
- this.#wrap = wrap !== undefined
- ? wrap
- : Math.abs(dirMin - dirMax) >= Math.PI * 2 - Number.EPSILON;
- const [min, max] = dirMin < dirMax ? [dirMin, dirMax] : [dirMax , dirMin];
- this.#rangeElem.setAttribute('d', arc(0, 0, 28.87, min, max));
- }
-}
-
-// deg2rad
-// where is 0
-// range (0, 360), (-180, +180), (0,0) Really this is a range
-
-class Direction extends PopDownController {
- #options;
- constructor(object, property, options) {
- super(object, property, 'muigui-direction');
-this.#options = options; // FIX
- this.addTop(new NumberView(this,
-identity));
- this.addBottom(new DirectionView(this, options));
- this.updateDisplay();
- }
-}
-
-class RadioGridView extends EditView {
- #values;
-
- constructor(setter, keyValues, cols = 3) {
- const values = [];
- const name = makeId();
- super(createElem('div', {}, keyValues.map(([key, value], ndx) => {
- values.push(value);
- return createElem('label', {}, [
- createElem('input', {
- type: 'radio',
- name,
- value: ndx,
- onChange: function() {
- if (this.checked) {
- setter.setFinalValue(that.#values[this.value]);
- }
- },
- }),
- createElem('button', {
- type: 'button',
- textContent: key,
- onClick: function() {
- this.previousElementSibling.click();
- },
- }),
- ]);
- })));
- const that = this;
- this.#values = values;
- this.cols(cols);
- }
- updateDisplay(v) {
- const ndx = this.#values.indexOf(v);
- for (let i = 0; i < this.domElement.children.length; ++i) {
- this.domElement.children[i].children[0].checked = i === ndx;
- }
- }
- cols(cols) {
- this.domElement.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
- }
-}
-
-class RadioGrid extends ValueController {
- constructor(object, property, options) {
- super(object, property, 'muigui-radio-grid');
- const valueIsNumber = typeof this.getValue() === 'number';
- const {
- keyValues: keyValuesInput,
- cols = 3,
- } = options;
- const keyValues = convertToKeyValues(keyValuesInput, valueIsNumber);
- this.add(new RadioGridView(this, keyValues, cols));
- this.updateDisplay();
- }
-}
-
-function onResize(elem, callback) {
- new ResizeObserver(() => {
- callback({rect: elem.getBoundingClientRect(), elem});
- }).observe(elem);
-}
-
-function onResizeSVGNoScale(elem, hAnchor, vAnchor, callback) {
- onResize(elem, ({rect}) => {
- const {width, height} = rect;
- elem.setAttribute('viewBox', `-${width * hAnchor} -${height * vAnchor} ${width} ${height}`);
- callback({elem, rect});
- });
-}
-
-function onResizeCanvas(elem, callback) {
- onResize(elem, ({rect}) => {
- const {width, height} = rect;
- elem.width = width;
- elem.height = height;
- callback({elem, rect});
- });
-}
-
-const svg$1 = `
-
-`;
-
-function createSVGTicks(start, end, step, min, max, height) {
- const p = [];
- if (start < min) {
- start += stepify(min - start, v => v, step);
- }
- end = Math.min(end, max);
- for (let i = start; i <= end; i += step) {
- p.push(`M${i} 0 l0 ${height}`);
- }
- return p.join(' ');
-}
-
-function createSVGNumbers(start, end, unitSize, unit, minusSize, min, max, labelFn) {
- const texts = [];
- if (start < min) {
- start += stepify(min - start, v => v, unitSize);
- }
- end = Math.min(end, max);
- const digits = Math.max(0, -Math.log10(unit));
- const f = v => labelFn(v.toFixed(digits));
- for (let i = start; i <= end; i += unitSize) {
- texts.push(`${f(i / unitSize * unit)}`);
- }
- return texts.join('\n');
-}
-
-function computeSizeOfMinus(elem) {
- const oldHTML = elem.innerHTML;
- elem.innerHTML = '- ';
- const text = elem.querySelector('text');
- const size = text.getComputedTextLength();
- elem.innerHTML = oldHTML;
- return size;
-}
-
-class SliderView extends EditView {
- #svgElem;
- #originElem;
- #ticksElem;
- #thicksElem;
- #numbersElem;
- #leftGradElem;
- #rightGradElem;
- #width;
- #height;
- #lastV;
- #minusSize;
- #options = {
- min: -100,
- max: 100,
- step: 1,
- unit: 10,
- unitSize: 10,
- ticksPerUnit: 5,
- labelFn: v => v,
- tickHeight: 1,
- limits: true,
- thicksColor: undefined,
- orientation: undefined,
- };
-
- constructor(setter, options) {
- const wheelHelper = createWheelHelper();
- super(createElem('div', {
- innerHTML: svg$1,
- className: 'muigui-no-v-scroll',
- onWheel: e => {
- e.preventDefault();
- const {min, max, step} = this.#options;
- const delta = wheelHelper(e, step);
- const newV = clamp$1(stepify(this.#lastV + delta, v => v, step), min, max);
- setter.setValue(newV);
- },
- }));
- this.#svgElem = this.$('svg');
- this.#originElem = this.$('#muigui-origin');
- this.#ticksElem = this.$('#muigui-ticks');
- this.#thicksElem = this.$('#muigui-thicks');
- this.#numbersElem = this.$('#muigui-numbers');
- this.#leftGradElem = this.$('#muigui-left-grad');
- this.#rightGradElem = this.$('#muigui-right-grad');
- this.setOptions(options);
- let startV;
- addTouchEvents(this.domElement, {
- onDown: () => {
- startV = this.#lastV;
- },
- onMove: (e) => {
- const {min, max, unitSize, unit, step} = this.#options;
- const newV = clamp$1(stepify(startV - e.dx / unitSize * unit, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- addKeyboardEvents(this.domElement, {
- onDown: (e) => {
- const {min, max, step} = this.#options;
- const newV = clamp$1(stepify(this.#lastV + e.dx * step, v => v, step), min, max);
- setter.setValue(newV);
- },
- });
- onResizeSVGNoScale(this.#svgElem, 0.5, 0, ({rect: {width}}) => {
- this.#leftGradElem.setAttribute('x', -width / 2);
- this.#rightGradElem.setAttribute('x', width / 2 - 20);
- this.#minusSize = computeSizeOfMinus(this.#numbersElem);
- this.#width = width;
- this.#updateSlider();
- });
- }
- // |--------V--------|
- // . . | . . . | . . . |
- //
- #updateSlider() {
- // There's no size if ResizeObserver has not fired yet.
- if (!this.#width || this.#lastV === undefined) {
- return;
- }
- const {
- labelFn,
- limits,
- min,
- max,
- orientation,
- tickHeight,
- ticksPerUnit,
- unit,
- unitSize,
- thicksColor,
- } = this.#options;
- const unitsAcross = Math.ceil(this.#width / unitSize);
- const center = this.#lastV;
- const centerUnitSpace = center / unit;
- const startUnitSpace = Math.round(centerUnitSpace - unitsAcross);
- const endUnitSpace = startUnitSpace + unitsAcross * 2;
- const start = startUnitSpace * unitSize;
- const end = endUnitSpace * unitSize;
- const minUnitSpace = limits ? min * unitSize / unit : start;
- const maxUnitSpace = limits ? max * unitSize / unit : end;
- const height = labelFn(1) === '' ? 10 : 5;
- if (ticksPerUnit > 1) {
- this.#ticksElem.setAttribute('d', createSVGTicks(start, end, unitSize / ticksPerUnit, minUnitSpace, maxUnitSpace, height * tickHeight));
- }
- this.#thicksElem.style.stroke = thicksColor; //setAttribute('stroke', thicksColor);
- this.#thicksElem.setAttribute('d', createSVGTicks(start, end, unitSize, minUnitSpace, maxUnitSpace, height));
- this.#numbersElem.innerHTML = createSVGNumbers(start, end, unitSize, unit, this.#minusSize, minUnitSpace, maxUnitSpace, labelFn);
- this.#originElem.setAttribute('transform', `translate(${-this.#lastV * unitSize / unit} 0)`);
- this.#svgElem.classList.toggle('muigui-slider-up', orientation === 'up');
- }
- updateDisplay(v) {
- this.#lastV = v;
- this.#updateSlider();
- }
- setOptions(options) {
- copyExistingProperties(this.#options, options);
- return this;
- }
-}
-
-class Slider extends ValueController {
- constructor(object, property, options = {}) {
- super(object, property, 'muigui-slider');
- this.add(new SliderView(this, options));
- this.add(new NumberView(this, options));
- this.updateDisplay();
- }
-}
-
-const svg = `
-
-`;
-
-class Vec2View extends EditView {
- #svgElem;
- #arrowElem;
- #circleElem;
- #lastV = [];
-
- constructor(setter) {
- super(createElem('div', {
- innerHTML: svg,
- className: 'muigui-no-scroll',
- }));
- const onTouch = (e) => {
- const {width, height} = this.#svgElem.getBoundingClientRect();
- const nx = e.nx * 2 - 1;
- const ny = e.ny * 2 - 1;
- setter.setValue([nx * width * 0.5, ny * height * 0.5]);
- };
- addTouchEvents(this.domElement, {
- onDown: onTouch,
- onMove: onTouch,
- });
- this.#svgElem = this.$('svg');
- this.#arrowElem = this.$('#muigui-arrow');
- this.#circleElem = this.$('#muigui-circle');
- onResizeSVGNoScale(this.#svgElem, 0.5, 0.5, () => this.#updateDisplayImpl);
- }
- #updateDisplayImpl() {
- const [x, y] = this.#lastV;
- this.#arrowElem.setAttribute('d', `M0,0L${x},${y}`);
- this.#circleElem.setAttribute('transform', `translate(${x}, ${y})`);
- }
- updateDisplay(v) {
- this.#lastV[0] = v[0];
- this.#lastV[1] = v[1];
- this.#updateDisplayImpl();
- }
-}
-
-// TODO: zoom with wheel and pinch?
-// TODO: grid?
-// // options
-// scale:
-// range: number (both x and y + /)
-// range: array (min, max)
-// xRange:
-// deg/rad/turn
-
-class Vec2 extends PopDownController {
- constructor(object, property) {
- super(object, property, 'muigui-vec2');
-
- const makeSetter = (ndx) => {
- return {
- setValue: (v) => {
- const newV = this.getValue();
- newV[ndx] = v;
- this.setValue(newV);
- },
- setFinalValue: (v) => {
- const newV = this.getValue();
- newV[ndx] = v;
- this.setFinalValue(newV);
- },
- };
- };
-
- this.addTop(new NumberView(makeSetter(0), {
- converters: {
- to: v => v[0],
- from: strToNumber.from,
- },
- }));
- this.addTop(new NumberView(makeSetter(1), {
- converters: {
- to: v => v[1],
- from: strToNumber.from,
- },
- }));
- this.addBottom(new Vec2View(this));
- this.updateDisplay();
- }
-}
-
-export { ColorChooser, Direction, RadioGrid, Range, Select, Slider, TextNumber, Vec2, GUI as default };
-//# sourceMappingURL=muigui.module.js.map
diff --git a/dist/0.x/muigui.module.min.js b/dist/0.x/muigui.module.min.js
deleted file mode 100644
index ed1d8b4..0000000
--- a/dist/0.x/muigui.module.min.js
+++ /dev/null
@@ -1,2 +0,0 @@
-var t={default:'\n.muigui {\n --bg-color: #ddd;\n --color: #222;\n --contrast-color: #eee;\n --value-color: #145 ;\n --value-bg-color: #eeee;\n --disabled-color: #999;\n --menu-bg-color: #f8f8f8;\n --menu-sep-color: #bbb;\n --hover-bg-color: #999;\n --focus-color: #68C;\n --range-color: #888888;\n --invalid-color: #FF0000;\n --selected-color: rgb(255, 255, 255, 0.9);\n\n --button-bg-color: var(--value-bg-color);\n\n --range-left-color: var(--value-color);\n --range-right-color: var(--value-bg-color); \n --range-right-hover-color: var(--hover-bg-color);\n\n color: var(--color);\n background-color: var(--bg-color);\n}\n\n@media (prefers-color-scheme: dark) {\n .muigui {\n --bg-color: #222222;\n --color: #dddddd;\n --contrast-color: #000;\n --value-color: #43e5f7;\n --value-bg-color: #444444;\n --disabled-color: #666666;\n --menu-bg-color: #080808;\n --menu-sep-color: #444444;\n --hover-bg-color: #666666;\n --focus-color: #88AAFF;\n --range-color: #888888;\n --invalid-color: #FF6666;\n --selected-color: rgba(255, 255, 255, 0.3);\n\n --button-bg-color: var(--value-bg-color);\n\n --range-left-color: var(--value-color);\n --range-right-color: var(--value-bg-color); \n --range-right-hover-color: var(--hover-bg-color);\n\n color: var(--color);\n background-color: var(--bg-color);\n }\n}\n\n.muigui {\n --width: 250px;\n --label-width: 45%;\n --number-width: 40%;\n\n\n --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;\n --font-size: 11px;\n --font-family-mono: Menlo, Monaco, Consolas, "Droid Sans Mono", monospace;\n --font-size-mono: 11px;\n\n --line-height: 1.7em;\n --border-radius: 0px;\n\n width: var(--width);\n font-family: var(--font-family);\n font-size: var(--font-size);\n box-sizing: border-box;\n line-height: 100%;\n}\n.muigui * {\n box-sizing: inherit;\n}\n\n.muigui-no-scroll {\n touch-action: none;\n}\n.muigui-no-h-scroll {\n touch-action: pan-y;\n}\n.muigui-no-v-scroll {\n touch-action: pan-x;\n}\n\n.muigui-invalid-value {\n background-color: red !important;\n color: white !important;\n}\n\n.muigui-grid {\n display: grid;\n}\n.muigui-rows {\n display: flex;\n flex-direction: column;\n\n min-height: 20px;\n border: 2px solid red;\n}\n.muigui-columns {\n display: flex;\n flex-direction: row;\n\n height: 20px;\n border: 2px solid green;\n}\n.muigui-rows>*,\n.muigui-columns>* {\n flex: 1 1 auto;\n align-items: stretch;\n min-height: 0;\n min-width: 0;\n}\n\n.muigui-row {\n border: 2px solid yellow;\n min-height: 10px\n}\n.muigui-column {\n border: 2px solid lightgreen;\n}\n\n/* -------- */\n\n.muigui-show { /* */ }\n.muigui-hide { \n display: none !important;\n}\n.muigui-disabled {\n pointer-events: none;\n --color: var(--disabled-color) !important;\n --value-color: var(--disabled-color) !important;\n --range-left-color: var(--disabled-color) !important;\n}\n\n.muigui canvas,\n.muigui svg {\n display: block;\n border-radius: var(--border-radius);\n}\n.muigui canvas {\n background-color: var(--value-bg-color);\n}\n\n.muigui-controller {\n min-width: 0;\n min-height: var(--line-height);\n}\n.muigui-root,\n.muigui-menu {\n display: flex;\n flex-direction: column;\n position: relative;\n user-select: none;\n height: fit-content;\n margin: 0;\n padding-bottom: 0.1em;\n border-radius: var(--border-radius);\n}\n.muigui-menu {\n border-bottom: 1px solid var(--menu-sep-color);\n}\n\n.muigui-root>button:nth-child(1),\n.muigui-menu>button:nth-child(1) {\n border-top: 1px solid var(--menu-sep-color);\n border-bottom: 1px solid var(--menu-sep-color);\n position: relative;\n text-align: left;\n color: var(--color);\n background-color: var(--menu-bg-color);\n min-height: var(--line-height);\n padding-top: 0.2em;\n padding-bottom: 0.2em;\n cursor: pointer;\n border-radius: var(--border-radius);\n}\n.muigui-root>div:nth-child(2),\n.muigui-menu>div:nth-child(2) {\n flex: 1 1 auto;\n}\n\n.muigui-controller {\n margin-left: 0.2em;\n margin-right: 0.2em;\n}\n.muigui-root.muigui-controller,\n.muigui-menu.muigui-controller {\n margin-left: 0;\n margin-right: 0;\n}\n.muigui-controller>*:nth-child(1) {\n flex: 1 0 var(--label-width);\n min-width: 0;\n white-space: pre;\n}\n.muigui-controller>label:nth-child(1) {\n place-content: center start;\n display: inline-grid;\n overflow: hidden;\n}\n.muigui-controller>*:nth-child(2) {\n flex: 1 1 75%;\n min-width: 0;\n}\n\n/* -----------------------------------------\n a label controller is [[label][value]]\n*/\n\n.muigui-label-controller {\n display: flex;\n margin: 0.4em 0 0.4em 0;\n word-wrap: initial;\n align-items: stretch;\n}\n\n.muigui-value {\n display: flex;\n align-items: stretch;\n}\n.muigui-value>* {\n flex: 1 1 auto;\n min-width: 0;\n}\n.muigui-value>*:nth-child(1) {\n flex: 1 1 calc(100% - var(--number-width));\n}\n.muigui-value>*:nth-child(2) {\n flex: 1 1 var(--number-width);\n margin-left: 0.2em;\n}\n\n/* fix! */\n.muigui-open>button>label::before,\n.muigui-closed>button>label::before {\n width: 1.25em;\n height: var(--line-height);\n display: inline-grid;\n place-content: center start;\n pointer-events: none;\n}\n.muigui-open>button>label::before {\n content: "โง"; /*"โผ";*/\n}\n.muigui-closed>button>label::before {\n content: "โจ"; /*"โถ";*/\n}\n.muigui-open>*:nth-child(2) {\n transition: max-height 0.2s ease-out,\n opacity 0.5s ease-out;\n max-height: 100vh;\n overflow: auto;\n opacity: 1;\n}\n\n.muigui-closed>*:nth-child(2) {\n transition: max-height 0.2s ease-out,\n opacity 1s;\n max-height: 0;\n opacity: 0;\n overflow: hidden;\n}\n\n/* ---- popdown ---- */\n\n.muigui-pop-down-top {\n display: flex;\n}\n/* fix? */\n.muigui-value>*:nth-child(1).muigui-pop-down-top {\n flex: 0;\n}\n.muigui-pop-down-bottom {\n\n}\n\n.muigui-pop-down-values {\n min-width: 0;\n display: flex;\n}\n.muigui-pop-down-values>* {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.muigui-value.muigui-pop-down-controller {\n flex-direction: column;\n}\n\n.muigui-pop-down-top input[type=checkbox] {\n -webkit-appearance: none;\n appearance: none;\n width: auto;\n color: var(--value-color);\n background-color: var(--value-bg-color);\n cursor: pointer;\n\n display: grid;\n place-content: center;\n margin: 0;\n font: inherit;\n color: currentColor;\n width: 1.7em;\n height: 1.7em;\n transform: translateY(-0.075em);\n}\n\n.muigui-pop-down-top input[type=checkbox]::before {\n content: "+";\n display: grid;\n place-content: center;\n border-radius: calc(var(--border-radius) + 2px);\n border-left: 1px solid rgba(255,255,255,0.3);\n border-top: 1px solid rgba(255,255,255,0.3);\n border-bottom: 1px solid rgba(0,0,0,0.2);\n border-right: 1px solid rgba(0,0,0,0.2);\n background-color: var(--range-color);\n color: var(--value-bg-color);\n width: calc(var(--line-height) - 4px);\n height: calc(var(--line-height) - 4px);\n}\n\n.muigui-pop-down-top input[type=checkbox]:checked::before {\n content: "๏ผธ";\n}\n\n\n/* ---- select ---- */\n\n.muigui select,\n.muigui option,\n.muigui input,\n.muigui button {\n color: var(--value-color);\n background-color: var(--value-bg-color);\n font-family: var(--font-family);\n font-size: var(--font-size);\n border: none;\n margin: 0;\n border-radius: var(--border-radius);\n}\n.muigui select {\n appearance: none;\n margin: 0;\n margin-left: 0; /*?*/\n overflow: hidden; /* Safari */\n}\n\n.muigui select:focus,\n.muigui input:focus,\n.muigui button:focus {\n outline: 1px solid var(--focus-color);\n}\n\n.muigui select:hover,\n.muigui option:hover,\n.muigui input:hover,\n.muigui button:hover {\n background-color: var(--hover-bg-color); \n}\n\n/* ------ [ label ] ------ */\n\n.muigui-label {\n border-top: 1px solid var(--menu-sep-color);\n border-bottom: 1px solid var(--menu-sep-color);\n padding-top: 0.4em;\n padding-bottom: 0.3em;\n place-content: center start;\n background-color: var(--menu-bg-color);\n white-space: pre;\n border-radius: var(--border-radius);\n}\n\n/* ------ [ divider] ------ */\n\n.muigui-divider {\n min-height: 6px;\n border-top: 2px solid var(--menu-sep-color);\n margin-top: 6px;\n}\n\n/* ------ [ button ] ------ */\n\n.muigui-button {\n display: grid;\n\n}\n.muigui-button button {\n border: none;\n color: var(--value-color);\n background-color: var(--button-bg-color);\n cursor: pointer;\n place-content: center center;\n}\n\n/* ------ [ color ] ------ */\n\n.muigui-color>div {\n overflow: hidden;\n position: relative;\n margin-left: 0;\n margin-right: 0; /* why? */\n max-width: var(--line-height);\n border-radius: var(--border-radius);\n}\n\n.muigui-color>div:focus-within {\n outline: 1px solid var(--focus-color);\n}\n\n.muigui-color input[type=color] {\n border: none;\n padding: 0;\n background: inherit;\n cursor: pointer;\n position: absolute;\n width: 200%;\n left: -10px;\n top: -10px;\n height: 200%;\n}\n.muigui-disabled canvas,\n.muigui-disabled svg,\n.muigui-disabled img,\n.muigui-disabled .muigui-color input[type=color] {\n opacity: 0.2;\n}\n\n/* ------ [ checkbox ] ------ */\n\n.muigui-checkbox>label:nth-child(2) {\n display: grid;\n place-content: center start;\n margin: 0;\n}\n\n.muigui-checkbox input[type=checkbox] {\n -webkit-appearance: none;\n appearance: none;\n width: auto;\n color: var(--value-color);\n background-color: var(--value-bg-color);\n cursor: pointer;\n\n display: grid;\n place-content: center;\n margin: 0;\n font: inherit;\n color: currentColor;\n width: 1.7em;\n height: 1.7em;\n transform: translateY(-0.075em);\n}\n\n.muigui-checkbox input[type=checkbox]::before {\n content: "";\n color: var(--value-color);\n display: grid;\n place-content: center;\n}\n\n.muigui-checkbox input[type=checkbox]:checked::before {\n content: "โ";\n}\n\n.muigui input[type=number]::-webkit-inner-spin-button, \n.muigui input[type=number]::-webkit-outer-spin-button { \n -webkit-appearance: none;\n appearance: none;\n margin: 0; \n}\n.muigui input[type=number] {\n -moz-appearance: textfield;\n}\n\n/* ------ [ radio grid ] ------ */\n\n.muigui-radio-grid>div {\n display: grid;\n gap: 2px;\n}\n\n.muigui-radio-grid input {\n appearance: none;\n display: none;\n}\n\n.muigui-radio-grid button {\n color: var(--color);\n width: 100%;\n text-align: left;\n}\n\n.muigui-radio-grid input:checked + button {\n color: var(--value-color);\n background-color: var(--selected-color);\n}\n\n/* ------ [ color-chooser ] ------ */\n\n.muigui-color-chooser-cursor {\n stroke-width: 1px;\n stroke: white;\n fill: none;\n}\n.muigui-color-chooser-circle {\n stroke-width: 1px;\n stroke: white;\n fill: none;\n}\n\n\n/* ------ [ vec2 ] ------ */\n\n.muigui-vec2 svg {\n background-color: var(--value-bg-color);\n}\n\n.muigui-vec2-axis {\n stroke: 1px;\n stroke: var(--focus-color);\n}\n\n.muigui-vec2-line {\n stroke-width: 1px;\n stroke: var(--value-color);\n fill: var(--value-color);\n}\n\n/* ------ [ direction ] ------ */\n\n.muigui-direction svg {\n background-color: rgba(0,0,0,0.2);\n}\n\n.muigui-direction:focus-within svg {\n outline: none;\n}\n.muigui-direction-range {\n fill: var(--value-bg-color);\n}\n.muigui-direction svg:focus {\n outline: none;\n}\n.muigui-direction svg:focus .muigui-direction-range {\n stroke-width: 0.5px;\n stroke: var(--focus-color);\n}\n\n.muigui-direction-arrow {\n fill: var(--value-color);\n}\n\n/* ------ [ slider ] ------ */\n\n.muigui-slider>div {\n display: flex;\n align-items: stretch;\n height: var(--line-height);\n}\n.muigui-slider svg {\n flex: 1 1 auto;\n}\n.muigui-slider .muigui-slider-up #muigui-orientation {\n transform: scale(1, -1) translateY(-100%);\n}\n\n.muigui-slider .muigui-slider-up #muigui-number-orientation {\n transform: scale(1,-1);\n}\n\n.muigui-ticks {\n stroke: var(--range-color);\n}\n.muigui-thicks {\n stroke: var(--color);\n stroke-width: 2px;\n}\n.muigui-svg-text {\n fill: var(--color);\n font-size: 7px;\n}\n.muigui-mark {\n fill: var(--value-color);\n}\n\n/* ------ [ range ] ------ */\n\n\n.muigui-range input[type=range] {\n -webkit-appearance: none;\n appearance: none;\n background-color: transparent;\n}\n\n.muigui-range input[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n border-radius: calc(var(--border-radius) + 2px);\n border-left: 1px solid rgba(255,255,255,0.3);\n border-top: 1px solid rgba(255,255,255,0.3);\n border-bottom: 1px solid rgba(0,0,0,0.2);\n border-right: 1px solid rgba(0,0,0,0.2);\n background-color: var(--range-color);\n margin-top: calc((var(--line-height) - 2px) / -2);\n width: calc(var(--line-height) - 2px);\n height: calc(var(--line-height) - 2px);\n}\n\n.muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: none;\n appearance: none;\n border: 1px solid var(--menu-sep-color);\n height: 2px;\n}\n\n\n/* dat.gui style - doesn\'t work on Safari iOS */\n\n/*\n.muigui-range input[type=range] {\n cursor: ew-resize;\n overflow: hidden;\n}\n\n.muigui-range input[type=range] {\n -webkit-appearance: none;\n appearance: none;\n background-color: var(--range-right-color);\n margin: 0;\n}\n.muigui-range input[type=range]:hover {\n background-color: var(--range-right-hover-color);\n}\n\n.muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: none;\n appearance: none;\n height: max-content;\n color: var(--range-left-color);\n margin-top: -1px;\n}\n\n.muigui-range input[type=range]::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 0px;\n height: max-content;\n box-shadow: -1000px 0 0 1000px var(--range-left-color);\n}\n*/\n\n/* FF */\n/*\n.muigui-range input[type=range]::-moz-slider-progress {\n background-color: var(--range-left-color); \n}\n.muigui-range input[type=range]::-moz-slider-thumb {\n height: max-content;\n width: 0;\n border: none;\n box-shadow: -1000px 0 0 1000px var(--range-left-color);\n box-sizing: border-box;\n}\n*/\n\n.muigui-checkered-background {\n background-color: #404040;\n background-image:\n linear-gradient(45deg, #808080 25%, transparent 25%),\n linear-gradient(-45deg, #808080 25%, transparent 25%),\n linear-gradient(45deg, transparent 75%, #808080 75%),\n linear-gradient(-45deg, transparent 75%, #808080 75%);\n background-size: 16px 16px;\n background-position: 0 0, 0 8px, 8px -8px, -8px 0px;\n}\n\n/* ---------------------------------------------------------- */\n\n/* needs to be at bottom to take precedence */\n.muigui-auto-place {\n max-height: 100%;\n position: fixed;\n top: 0;\n right: 15px;\n z-index: 100001;\n}\n\n',themes:{default:"",float:"\n :root {\n color-scheme: light dark,\n }\n\n .muigui {\n --width: 400px;\n --bg-color: initial;\n --label-width: 25%;\n --number-width: 20%;\n }\n\n input,\n .muigui-label-controller>label {\n text-shadow:\n -1px -1px 0 var(--contrast-color),\n 1px -1px 0 var(--contrast-color),\n -1px 1px 0 var(--contrast-color),\n 1px 1px 0 var(--contrast-color);\n }\n\n .muigui-controller > label:nth-child(1) {\n place-content: center end;\n margin-right: 1em;\n }\n\n .muigui-value > :nth-child(2) {\n margin-left: 1em;\n }\n\n .muigui-root>*:nth-child(1) {\n display: none;\n }\n\n .muigui-range input[type=range]::-webkit-slider-thumb {\n border-radius: 1em;\n }\n\n .muigui-range input[type=range]::-webkit-slider-runnable-track {\n -webkit-appearance: initial;\n appearance: none;\n border: 1px solid rgba(0, 0, 0, 0.25);\n height: 2px;\n }\n\n .muigui-colors {\n --value-color: var(--color );\n --value-bg-color: rgba(0, 0, 0, 0.1);\n --disabled-color: #cccccc;\n --menu-bg-color: rgba(0, 0, 0, 0.1);\n --menu-sep-color: #bbbbbb;\n --hover-bg-color: rgba(0, 0, 0, 0);\n --invalid-color: #FF0000;\n --selected-color: rgba(0, 0, 0, 0.3);\n --range-color: rgba(0, 0, 0, 0.125);\n }\n"}};function e(t,e={},n=[]){const i=document.createElement(t);return function(t,e,n){for(const[n,i]of Object.entries(e))if("function"==typeof i&&n.startsWith("on")){const e=n.substring(2).toLowerCase();t.addEventListener(e,i,{passive:!1})}else if("object"==typeof i)for(const[e,o]of Object.entries(i))t[n][e]=o;else void 0===t[n]?t.setAttribute(n,i):t[n]=i;for(const e of n)t.appendChild(e)}(i,e,n),i}let n=0;function i(t,e){const n=t.indexOf(e);return n&&t.splice(n,1),t}function o(t,e,n){return Math.max(e,Math.min(n,t))}const r="undefined"!=typeof SharedArrayBuffer?function(t){return t&&t.buffer&&(t.buffer instanceof ArrayBuffer||t.buffer instanceof SharedArrayBuffer)}:function(t){return t&&t.buffer&&t.buffer instanceof ArrayBuffer},s=(t,e,n)=>Math.round(e(t)/n)/(1/n),a=(t,e)=>(t%e+e)%e;function l(t,e){for(const n in e)n in t&&(t[n]=e[n]);return t}const u=(t,e,n,i,o)=>(t-e)*(o-i)/(n-e)+i,c=({from:t,to:e})=>({to:n=>u(n,...t,...e),from:n=>[!0,u(n,...e,...t)]}),h=({from:t,to:e,step:n})=>({min:e[0],max:e[1],...n&&{step:n},converters:c({from:t,to:e})}),d={to:t=>t,from:t=>[!0,t]};function p(t,e,n,i,o){const{converters:{from:r}=d}=o,{min:s,max:a}=o,l=o.minRange||0,u=r(l)[1],c=t.add(e,n,{...o,min:s,max:a-l}).onChange((t=>{h.setValue(Math.min(a,Math.max(t+u,e[i])))})),h=t.add(e,i,{...o,min:s+l,max:a}).onChange((t=>{c.setValue(Math.max(s,Math.min(t-u,e[n])))}));return[c,h]}class m{domElement;#t;#e=[];constructor(t){this.domElement=t,this.#t=t}addElem(t){return this.#t.appendChild(t),t}removeElem(t){return this.#t.removeChild(t),t}pushSubElem(t){this.#t.appendChild(t),this.#t=t}popSubElem(){this.#t=this.#t.parentElement}add(t){return this.#e.push(t),this.addElem(t.domElement),t}remove(t){return this.removeElem(t.domElement),i(this.#e,t),t}pushSubView(t){this.pushSubElem(t.domElement)}popSubView(){this.popSubElem()}setOptions(t){for(const e of this.#e)e.setOptions(t)}updateDisplayIfNeeded(t,e){for(const n of this.#e)n.updateDisplayIfNeeded(t,e);return this}$(t){return this.domElement.querySelector(t)}}class g extends m{#n;#i;#o;constructor(t){super(e("div",{className:"muigui-controller"})),this.#n=[],this.#i=[],t&&this.domElement.classList.add(t)}get parent(){return this.#o}setParent(t){this.#o=t,this.enable(!this.disabled())}show(t=!0){return this.domElement.classList.toggle("muigui-hide",!t),this.domElement.classList.toggle("muigui-show",t),this}hide(){return this.show(!1)}disabled(){return!!this.domElement.closest(".muigui-disabled")}enable(t=!0){return this.domElement.classList.toggle("muigui-disabled",!t),["input","button","select","textarea"].forEach((t=>{this.domElement.querySelectorAll(t).forEach((t=>{const e=!!t.closest(".muigui-disabled");t.disabled=e}))})),this}disable(t=!0){return this.enable(!t)}onChange(t){return this.removeChange(t),this.#n.push(t),this}removeChange(t){return i(this.#n,t),this}onFinishChange(t){return this.removeFinishChange(t),this.#i.push(t),this}removeFinishChange(t){return i(this.#i,t),this}#r(t,e){for(const n of t)n.call(this,e)}emitChange(t,e,n){this.#r(this.#n,t),this.#o&&(void 0===e?this.#o.emitChange(t):this.#o.emitChange({object:e,property:n,value:t,controller:this}))}emitFinalChange(t,e,n){this.#r(this.#i,t),this.#o&&(void 0===e?this.#o.emitChange(t):this.#o.emitFinalChange({object:e,property:n,value:t,controller:this}))}updateDisplay(){}getColors(){const t=t=>t.replace(/-([a-z])/g,((t,e)=>e.toUpperCase())),n=e("div");this.domElement.appendChild(n);const i=Object.fromEntries(["color","bg-color","value-color","value-bg-color","hover-bg-color","menu-bg-color","menu-sep-color","disabled-color"].map((e=>{n.style.color=`var(--${e})`;const i=getComputedStyle(n);return[t(e),i.color]})));return n.remove(),i}}class f extends g{#s;#a;#l;#u={name:""};constructor(t,n,i={}){super("muigui-button",""),this.#s=t,this.#a=n,this.#l=this.addElem(e("button",{type:"button",onClick:()=>{this.#s[this.#a](this)}})),this.setOptions({name:n,...i})}setOptions(t){l(this.#u,t);const{name:e}=this.#u;this.#l.textContent=e}}function b(t,e){if(t.length!==e.length)return!1;for(let n=0;n{t.setValue(i.checked)},onChange:()=>{t.setFinalValue(i.checked)}});super(e("label",{},[i])),this.#b=i}updateDisplay(t){this.#b.checked=t}}const w=[],y=new Set;let k,E;function $(){k=void 0,E=!0;for(const t of w)y.has(t)||t();E=!1,y.size&&(E?C():(y.forEach((t=>{i(w,t)})),y.clear())),C()}function C(){!k&&w.length&&(k=requestAnimationFrame($))}let V=0;function I(){return"muigui-"+ ++V}class M extends m{constructor(t=""){super(e("div",{className:"muigui-value"})),t&&this.domElement.classList.add(t)}}class S extends g{#v;#x;constructor(t="",n=""){super("muigui-label-controller"),this.#v=I(),this.#x=e("label",{for:this.#v}),this.domElement.appendChild(this.#x),this.pushSubView(new M(t)),this.name(n)}get id(){return this.#v}name(t){return this.#x.title===this.#x.textContent&&(this.#x.title=t),this.#x.textContent=t,this}tooltip(t){this.#x.title=t}}class D extends S{#s;#a;#w;#y;#e;#k;constructor(t,e,n=""){super(n,e),this.#s=t,this.#a=e,this.#w=this.getValue(),this.#y=!1,this.#e=[]}get initialValue(){return this.#w}get object(){return this.#s}get property(){return this.#a}add(t){return this.#e.push(t),super.add(t),this.updateDisplay(),t}#E(t,e){let n=!1;if("object"==typeof t){const e=this.#s[this.#a];if(Array.isArray(t)||r(t))for(let i=0;i=0&&w.splice(e,1)}(this.#k)),this}}class N extends D{constructor(t,e){super(t,e,"muigui-checkbox");const n=this.id;this.add(new x(this,n)),this.updateDisplay()}}const F={to:t=>t,from:t=>[!0,t]},A={to:t=>t.toString(),from:t=>{const e=parseFloat(t);return[!Number.isNaN(e),e]}},U={radToDeg:c({to:[0,180],from:[0,Math.PI]})};function L(){let t=0;return function(e,n,i=5){t-=e.deltaY*n/i;const o=Math.floor(Math.abs(t)/n)*Math.sign(t)*n;return t-=o,o}}class O extends v{#$;#C;#V;#I;#u={step:.01,converters:A,min:Number.NEGATIVE_INFINITY,max:Number.POSITIVE_INFINITY};constructor(t,n){const i=t.setValue.bind(t),r=t.setFinalValue.bind(t),a=L();super(e("input",{type:"number",onInput:()=>this.#M(i,!0),onChange:()=>this.#M(r,!1),onWheel:e=>{e.preventDefault();const{min:n,max:i,step:r}=this.#u,l=a(e,r),u=parseFloat(this.domElement.value),c=o(s(u+l,(t=>t),r),n,i);t.setValue(c)}})),this.setOptions(n)}#M(t,e){const n=parseFloat(this.domElement.value),[i,r]=this.#C(n);let s;if(i&&!Number.isNaN(n)){const{min:n,max:i}=this.#u;s=r>=n&&r<=i,this.#I=e,t(o(r,n,i))}this.domElement.classList.toggle("muigui-invalid-value",!i||!s)}updateDisplay(t){this.#I||(this.domElement.value=s(t,this.#$,this.#V)),this.#I=!1}setOptions(t){l(this.#u,t);const{step:e,converters:{to:n,from:i}}=this.#u;return this.#$=n,this.#C=i,this.#V=e,this}}class j extends D{#S;#V;constructor(t,e,n={}){super(t,e,"muigui-checkbox"),this.#S=this.add(new O(this,n)),this.updateDisplay()}}class T extends v{#D;constructor(t,n){const i=[];super(e("select",{onChange:()=>{t.setFinalValue(this.#D[this.domElement.selectedIndex])}},n.map((([t,n])=>(i.push(n),e("option",{textContent:t})))))),this.#D=i}updateDisplay(t){const e=this.#D.indexOf(t);this.domElement.selectedIndex=e}}function H(t,e){return Array.isArray(t)?Array.isArray(t[0])?t:e?t.map(((t,e)=>[t,e])):t.map((t=>[t,t])):[...Object.entries(t)]}class z extends D{constructor(t,e,n){super(t,e,"muigui-select");const i="number"==typeof this.getValue(),{keyValues:o}=n,r=H(o,i);this.add(new T(this,r)),this.updateDisplay()}}class P extends v{#$;#C;#V;#I;#u={step:.01,min:0,max:1,converters:F};constructor(t,n){const i=L();super(e("input",{type:"range",onInput:()=>{this.#I=!0;const{min:e,max:n,step:i}=this.#u,r=parseFloat(this.domElement.value),a=o(s(r,(t=>t),i),e,n),[l,u]=this.#C(a);l&&t.setValue(u)},onChange:()=>{this.#I=!0;const{min:e,max:n,step:i}=this.#u,r=parseFloat(this.domElement.value),a=o(s(r,(t=>t),i),e,n),[l,u]=this.#C(a);l&&t.setFinalValue(u)},onWheel:e=>{e.preventDefault();const[n,r]=this.#C(parseFloat(this.domElement.value));if(!n)return;const{min:a,max:l,step:u}=this.#u,c=i(e,u),h=o(s(r+c,(t=>t),u),a,l);t.setValue(h)}})),this.setOptions(n)}updateDisplay(t){this.#I||(this.domElement.value=s(t,this.#$,this.#V)),this.#I=!1}setOptions(t){l(this.#u,t);const{step:e,min:n,max:i,converters:{to:o,from:r}}=this.#u;return this.#$=o,this.#C=r,this.#V=e,this.domElement.step=e,this.domElement.min=n,this.domElement.max=i,this}}class B extends D{constructor(t,e,n){super(t,e,"muigui-range"),this.add(new P(this,n)),this.add(new O(this,n))}}class G extends v{#$;#C;#I;#u={converters:F};constructor(t,n){const i=t.setValue.bind(t),o=t.setFinalValue.bind(t);super(e("input",{type:"text",onInput:()=>this.#M(i,!0),onChange:()=>this.#M(o,!1)})),this.setOptions(n)}#M(t,e){const[n,i]=this.#C(this.domElement.value);n&&(this.#I=e,t(i)),this.domElement.style.color=n?"":"var(--invalid-color)"}updateDisplay(t){this.#I||(this.domElement.value=this.#$(t),this.domElement.style.color=""),this.#I=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n}}=this.#u;return this.#$=e,this.#C=n,this}}class R extends D{constructor(t,e){super(t,e,"muigui-checkbox"),this.add(new G(this)),this.updateDisplay()}}const _=(t,e,n)=>Math.max(e,Math.min(n,t)),Y=(t,e,n)=>t+(e-t)*n,W=t=>t>=0?t%1:1-t%1,q=t=>+t.toFixed(0),K=t=>+t.toFixed(3),J=t=>parseInt(t.substring(1,3),16)<<16|parseInt(t.substring(3,5),16)<<8|parseInt(t.substring(5,7),16),X=t=>parseInt(t.substring(1,3),16)*2**24+65536*parseInt(t.substring(3,5),16)+256*parseInt(t.substring(5,7),16)+parseInt(t.substring(7,9),16),Z=t=>[parseInt(t.substring(1,3),16),parseInt(t.substring(3,5),16),parseInt(t.substring(5,7),16)],Q=t=>`#${Array.from(t).map((t=>t.toString(16).padStart(2,"0"))).join("")}`,tt=t=>[parseInt(t.substring(1,3),16),parseInt(t.substring(3,5),16),parseInt(t.substring(5,7),16),parseInt(t.substring(7,9),16)],et=t=>`#${Array.from(t).map((t=>t.toString(16).padStart(2,"0"))).join("")}`,nt=t=>Z(t).map((t=>K(t/255))),it=t=>Q(Array.from(t).map((t=>Math.round(_(255*t,0,255))))),ot=t=>tt(t).map((t=>K(t/255))),rt=t=>et(Array.from(t).map((t=>Math.round(_(255*t,0,255))))),st=t=>_(Math.round(255*t),0,255).toString(16).padStart(2,"0"),at=t=>({r:parseInt(t.substring(1,3),16)/255,g:parseInt(t.substring(3,5),16)/255,b:parseInt(t.substring(5,7),16)/255}),lt=t=>({r:parseInt(t.substring(1,3),16)/255,g:parseInt(t.substring(3,5),16)/255,b:parseInt(t.substring(5,7),16)/255,a:parseInt(t.substring(7,9),16)/255}),ut=t=>`rgb(${Z(t).join(", ")})`,ct=/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,ht=t=>`rgba(${tt(t).map(((t,e)=>3===e?t/255:t)).join(", ")})`,dt=/^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+\.\d+|\d+)\s*\)\s*$/,pt=t=>{const e=yt(Z(t)).map((t=>q(t)));return`hsl(${e[0]}, ${e[1]}%, ${e[2]}%)`},mt=t=>{const e=kt(tt(t)).map(((t,e)=>3===e?K(t):q(t)));return`hsl(${e[0]} ${e[1]}% ${e[2]}% / ${e[3]})`},gt=/^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\)\s*$/,ft=/^\s*hsl\(\s*(\d+)(?:deg|)\s*(?:,|)\s*(\d+)%\s*(?:,|)\s*(\d+)%\s*\/\s*(\d+\.\d+|\d+)\s*\)\s*$/,bt=(t,e)=>(t%e+e)%e;function vt([t,e,n]){t=bt(t,360),e=_(e/100,0,1),n=_(n/100,0,1);const i=e*Math.min(n,1-n);function o(e){const o=(e+t/30)%12;return n-i*Math.max(-1,Math.min(o-3,9-o,1))}return[o(0),o(8),o(4)].map((t=>Math.round(255*t)))}function xt([t,e,n]){const i=Math.max(t,e,n),o=Math.min(t,e,n),r=.5*(o+i),s=i-o;let a=0,l=0;if(0!==s)switch(l=0===r||1===r?0:(i-r)/Math.min(r,1-r),i){case t:a=(e-n)/s+(e{const[e,n,i]=xt(t.map((t=>t/255)));return[360*e,100*n,100*i]},kt=t=>{const[e,n,i,o]=wt(t.map((t=>t/255)));return[360*e,100*n,100*i,o]};function Et([t,e,n]){return e=_(e,0,1),n=_(n,0,1),[t,t+2/3,t+1/3].map((t=>Y(1,_(Math.abs(6*W(t)-3)-1,0,1),e)*n))}function $t([t,e,n,i]){return[...Et([t,e,n]),i]}const Ct=t=>Math.round(1e3*t)/1e3;function Vt([t,e,n]){const i=n>e?[n,e,-1,2/3]:[e,n,0,-1/3],o=i[0]>t?[i[0],i[1],i[3],t]:[t,i[1],i[2],i[0]],r=o[0]-Math.min(o[3],o[1]);return[Math.abs(o[2]+(o[3]-o[1])/(6*r+Number.EPSILON)),r/(o[0]+Number.EPSILON),o[0]].map(Ct)}const It=t=>t.endsWith("a")||t.startsWith("hex8"),Mt=[{re:/^#(?:[0-9a-f]){6}$/i,format:"hex6"},{re:/^(?:[0-9a-f]){6}$/i,format:"hex6-no-hash"},{re:/^#(?:[0-9a-f]){8}$/i,format:"hex8"},{re:/^(?:[0-9a-f]){8}$/i,format:"hex8-no-hash"},{re:/^#(?:[0-9a-f]){3}$/i,format:"hex3"},{re:/^(?:[0-9a-f]){3}$/i,format:"hex3-no-hash"},{re:ct,format:"css-rgb"},{re:gt,format:"css-hsl"},{re:dt,format:"css-rgba"},{re:ft,format:"css-hsla"}];function St(t){switch(typeof t){case"number":return console.warn('can not reliably guess format based on a number. You should pass in a format like {format: "uint32-rgb"} or {format: "uint32-rgb"}'),t<=16777215?"uint32-rgb":"uint32-rgba";case"string":{const e=function(t){for(const e of Mt)if(e.re.test(t))return e}(t.trim());if(e)return e.format;break}case"object":if(t instanceof Uint8Array||t instanceof Uint8ClampedArray){if(3===t.length)return"uint8-rgb";if(4===t.length)return"uint8-rgba"}else if(t instanceof Float32Array){if(3===t.length)return"float-rgb";if(4===t.length)return"float-rgba"}else if(Array.isArray(t)){if(3===t.length)return"float-rgb";if(4===t.length)return"float-rgba"}else if("r"in t&&"g"in t&&"b"in t)return"a"in t?"object-rgba":"object-rgb"}throw new Error(`unknown color format: ${t}`)}function Dt(t){return t.trim(t)}function Nt(t){return t.trim(t)}function Ft(t){return t[1]===t[2]&&t[3]===t[4]&&t[5]===t[6]?`#${t[1]}${t[3]}${t[5]}`:t}const At=/^(#|)([0-9a-f]{3})$/i;function Ut(t){const e=At.exec(t);if(e){const[,,t]=e;return"#"+`${(n=t)[0]}${n[0]}${n[1]}${n[1]}${n[2]}${n[2]}`}var n;return t}function Lt(t){return Ft(Dt(t))}const Ot=t=>{const e=ct.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),`rgb(${n.join(", ")})`]},jt=t=>{const e=dt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map(((t,e)=>3===e?parseFloat(t):parseInt(t)));return[!n.find((t=>t>255)),`rgba(${n.join(", ")})`]},Tt=t=>{const e=gt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseFloat(t)));return[!n.find((t=>Number.isNaN(t))),`hsl(${n[0]}, ${n[1]}%, ${n[2]}%)`]},Ht=t=>{const e=ft.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map((t=>parseFloat(t)));return[!n.find((t=>Number.isNaN(t))),`hsl(${n[0]} ${n[1]}% ${n[2]}% / ${n[3]})`]},zt=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/,Pt=/^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*$/,Bt=/^\s*(?:0x){0,1}([0-9a-z]{1,6})\s*$/i,Gt=/^\s*(?:0x){0,1}([0-9a-z]{1,8})\s*$/i,Rt=/^\s*#[a-f0-9]{6}\s*$|^\s*#[a-f0-9]{3}\s*$/i,_t=/^\s*[a-f0-9]{6}\s*$/i,Yt=/^\s*#[a-f0-9]{8}\s*$/i,Wt=/^\s*[a-f0-9]{8}\s*$/i,qt={hex6:{color:{from:t=>[!0,t],to:Dt},text:{from:t=>[Rt.test(t),t.trim()],to:t=>t}},hex8:{color:{from:t=>[!0,t],to:Nt},text:{from:t=>[Yt.test(t),t.trim()],to:t=>t}},hex3:{color:{from:t=>[!0,Lt(t)],to:Ut},text:{from:t=>[Rt.test(t),Ft(t.trim())],to:t=>t}},"hex6-no-hash":{color:{from:t=>[!0,t.substring(1)],to:t=>`#${Dt(t)}`},text:{from:t=>[_t.test(t),t.trim()],to:t=>t}},"hex8-no-hash":{color:{from:t=>[!0,t.substring(1)],to:t=>`#${Nt(t)}`},text:{from:t=>[Wt.test(t),t.trim()],to:t=>t}},"hex3-no-hash":{color:{from:t=>[!0,Lt(t).substring(1)],to:Ut},text:{from:t=>[_t.test(t),Ft(t.trim())],to:t=>t}},"uint32-rgb":{color:{from:t=>[!0,J(t)],to:t=>`#${Math.round(t).toString(16).padStart(6,"0")}`},text:{from:t=>(t=>{const e=Bt.exec(t);return e?[!0,parseInt(e[1],16)]:[!1]})(t),to:t=>`0x${t.toString(16).padStart(6,"0")}`}},"uint32-rgba":{color:{from:t=>[!0,X(t)],to:t=>`#${Math.round(t).toString(16).padStart(8,"0")}`},text:{from:t=>(t=>{const e=Gt.exec(t);return e?[!0,parseInt(e[1],16)]:[!1]})(t),to:t=>`0x${t.toString(16).padStart(8,"0")}`}},"uint8-rgb":{color:{from:t=>[!0,Z(t)],to:Q},text:{from:t=>{const e=zt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),n]},to:t=>t.join(", ")}},"uint8-rgba":{color:{from:t=>[!0,tt(t)],to:et},text:{from:t=>{const e=Pt.exec(t);if(!e)return[!1];const n=[e[1],e[2],e[3],e[4]].map((t=>parseInt(t)));return[!n.find((t=>t>255)),n]},to:t=>t.join(", ")}},"float-rgb":{color:{from:t=>[!0,nt(t)],to:it},text:{from:t=>{const e=t.split(",").map((t=>t.trim())),n=e.map((t=>parseFloat(t)));if(3!==n.length)return[!1];const i=e.findIndex((t=>isNaN(t)));return[i<0,n.map((t=>K(t)))]},to:t=>Array.from(t).map((t=>K(t))).join(", ")}},"float-rgba":{color:{from:t=>[!0,ot(t)],to:rt},text:{from:t=>{const e=t.split(",").map((t=>t.trim())),n=e.map((t=>parseFloat(t)));if(4!==n.length)return[!1];const i=e.findIndex((t=>isNaN(t)));return[i<0,n.map((t=>K(t)))]},to:t=>Array.from(t).map((t=>K(t))).join(", ")}},"object-rgb":{color:{from:t=>[!0,at(t)],to:t=>`#${st(t.r)}${st(t.g)}${st(t.b)}`},text:{from:t=>{try{const e=t.replace(/([a-z])/g,'"$1"'),n=JSON.parse(e);if(Number.isNaN(n.r)||Number.isNaN(n.g)||Number.isNaN(n.b))throw new Error("not {r, g, b}");return[!0,n]}catch(t){return[!1]}},to:t=>`{r:${K(t.r)}, g:${K(t.g)}, b:${K(t.b)}}`}},"object-rgba":{color:{from:t=>[!0,lt(t)],to:t=>`#${st(t.r)}${st(t.g)}${st(t.b)}${st(t.a)}`},text:{from:t=>{try{const e=t.replace(/([a-z])/g,'"$1"'),n=JSON.parse(e);if(Number.isNaN(n.r)||Number.isNaN(n.g)||Number.isNaN(n.b)||Number.isNaN(n.a))throw new Error("not {r, g, b, a}");return[!0,n]}catch(t){return[!1]}},to:t=>`{r:${K(t.r)}, g:${K(t.g)}, b:${K(t.b)}}, a:${K(t.a)}}`}},"css-rgb":{color:{from:t=>[!0,ut(t)],to:t=>{const e=ct.exec(t);return Q([e[1],e[2],e[3]].map((t=>parseInt(t))))}},text:{from:Ot,to:t=>Ot(t)[1]}},"css-rgba":{color:{from:t=>[!0,ht(t)],to:t=>{const e=dt.exec(t);return et([e[1],e[2],e[3],e[4]].map(((t,e)=>3===e?255*parseFloat(t)|0:parseInt(t))))}},text:{from:jt,to:t=>jt(t)[1]}},"css-hsl":{color:{from:t=>[!0,pt(t)],to:t=>{const e=gt.exec(t),n=vt([e[1],e[2],e[3]].map((t=>parseFloat(t))));return Q(n)}},text:{from:Tt,to:t=>Tt(t)[1]}},"css-hsla":{color:{from:t=>[!0,mt(t)],to:t=>{const e=ft.exec(t),n=function([t,e,n,i]){return[...vt([t,e,n]),255*i|0]}([e[1],e[2],e[3],e[4]].map((t=>parseFloat(t))));return et(n)}},text:{from:Ht,to:t=>Ht(t)[1]}}};class Kt extends m{constructor(t,n){super(e(t,{className:n}))}}class Jt extends S{#N;constructor(){super("muigui-canvas"),this.#N=this.add(new Kt("canvas","muigui-canvas")).domElement}get canvas(){return this.#N}}class Xt extends v{#$;#C;#F;#I;#u={converters:F};constructor(t,n){const i=e("input",{type:"color",onInput:()=>{const[e,n]=this.#C(i.value);e&&(this.#I=!0,t.setValue(n))},onChange:()=>{const[e,n]=this.#C(i.value);e&&(this.#I=!0,t.setFinalValue(n))}});super(e("div",{},[i])),this.setOptions(n),this.#F=i}updateDisplay(t){this.#I||(this.#F.value=this.#$(t)),this.#I=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n}}=this.#u;return this.#$=e,this.#C=n,this}}class Zt extends D{#A;#S;constructor(t,e,n={}){super(t,e,"muigui-color");const i=n.format||St(this.getValue()),{color:o,text:r}=qt[i];this.#A=this.add(new Xt(this,{converters:o})),this.#S=this.add(new G(this,{converters:r})),this.updateDisplay()}setOptions(t){const{format:e}=t;if(e){const{color:t,text:n}=qt[e];this.#A.setOptions({converters:t}),this.#S.setOptions({converters:n})}return super.setOptions(t),this}}class Qt extends g{constructor(){super("muigui-divider")}}class te extends g{#U;#L;constructor(t){super(t),this.#U=[],this.#L=this}get children(){return this.#U}get controllers(){return this.#U.filter((t=>!(t instanceof te)))}get folders(){return this.#U.filter((t=>t instanceof te))}reset(t=!0){for(const e of this.#U)e instanceof te&&!t||e.reset(t);return this}updateDisplay(){for(const t of this.#U)t.updateDisplay();return this}remove(t){const e=this.#U.indexOf(t);if(e>=0){const t=this.#U.splice(e,1)[0];t.domElement.remove(),t.setParent(null)}return this}_addControllerImpl(t){return this.domElement.appendChild(t.domElement),this.#U.push(t),t.setParent(this),t}addController(t){return this.#L._addControllerImpl(t)}pushContainer(t){return this.addController(t),this.#L=t,t}popContainer(){return this.#L=this.#L.parent,this}}class ee extends te{#O;constructor(t="Controls",n="muigui-menu"){super(n),this.#O=e("label"),this.addElem(e("button",{type:"button",onClick:()=>this.toggleOpen()},[this.#O])),this.pushContainer(new te),this.name(t),this.open()}open(t=!0){return this.domElement.classList.toggle("muigui-closed",!t),this.domElement.classList.toggle("muigui-open",t),this}close(){return this.open(!1)}name(t){return this.#O.textContent=t,this}title(t){return this.name(t)}toggleOpen(){return this.open(!this.domElement.classList.contains("muigui-open")),this}}class ne extends g{constructor(t){super("muigui-label"),this.text(t)}text(t){return this.domElement.textContent=t,this}}function ie(){}function oe(t,e,n){const i=t.getBoundingClientRect(),o=e.clientX-i.left,r=e.clientY-i.top,s=o/i.width,a=r/i.height,l=o-(n=n||[o,r])[0],u=r-n[1];return{x:o,y:r,nx:s,ny:a,dx:l,dy:u,ndx:l/i.width,ndy:u/i.width}}function re(t,{onDown:e=ie,onMove:n=ie,onUp:i=ie}){let o;const r=function(e){const i={type:"move",...oe(t,e,o)};n(i)},s=function(e){t.releasePointerCapture(e.pointerId),t.removeEventListener("pointermove",r),t.removeEventListener("pointerup",s),document.body.style.backgroundColor="",i("up")},a=function(n){t.addEventListener("pointermove",r),t.addEventListener("pointerup",s),t.setPointerCapture(n.pointerId);const i=oe(t,n);o=[i.x,i.y],e({type:"down",...i})};return t.addEventListener("pointerdown",a),function(){t.removeEventListener("pointerdown",a)}}function se(t){return t.querySelectorAll("[data-src]").forEach((e=>{const i="muigui-id-"+n++;e.id=i,t.querySelectorAll(`[data-target=${e.dataset.src}]`).forEach((t=>{t.setAttribute("fill",`url(#${i})`)}))})),t}class ae extends v{#$;#C;#j;#T;#H;#z;#P;#B;#G;#R;#_;#Y;#W;#q;#u={converters:F,alpha:!1};#K;#J;constructor(t,n){super(e("div",{innerHTML:'\n\n\n\n',className:"muigui-no-scroll"})),this.#j=this.domElement.children[0],this.#H=this.domElement.children[1],this.#B=this.domElement.children[2],se(this.#j),se(this.#H),se(this.#B),this.#T=this.$(".muigui-color-chooser-circle"),this.#z=this.$("[data-src=muigui-color-chooser-hue]"),this.#P=this.$(".muigui-color-chooser-hue-cursor"),this.#G=this.$("[data-src=muigui-color-chooser-alpha]"),this.#R=this.$(".muigui-color-chooser-alpha-cursor");const i=e=>{const n=o(e.nx,0,1),i=o(e.ny,0,1);this.#_[1]=n,this.#_[2]=1-i,this.#Y=!0,this.#q=!0;const[r,s]=this.#C(this.#K(this.#_));r&&t.setValue(s)},r=e=>{const n=o(e.nx,0,1);this.#_[0]=n,this.#W=!0,this.#q=!0;const[i,r]=this.#C(this.#K(this.#_));i&&t.setValue(r)},s=e=>{const n=o(e.nx,0,1);this.#_[3]=n,this.#Y=!0,this.#W=!0;const[i,r]=this.#C(this.#K(this.#_));i&&t.setValue(r)};re(this.#j,{onDown:i,onMove:i}),re(this.#H,{onDown:r,onMove:r}),re(this.#B,{onDown:s,onMove:s}),this.setOptions(n)}updateDisplay(t){this.#_||(this.#_=this.#J(this.#$(t)));{const[e,n,i,o=1]=this.#J(this.#$(t));this.#Y||(this.#_[0]=n>.001&&i>.001?e:this.#_[0]),this.#W||(this.#_[1]=n,this.#_[2]=i),this.#q||(this.#_[3]=o)}{const[t,e,n,i]=this.#_,[o,r,s]=wt($t(this.#_));this.#Y||this.#P.setAttribute("transform",`translate(${64*t}, 0)`),this.#z.children[0].setAttribute("stop-color",`hsl(${360*o} 0% 100% / ${i})`),this.#z.children[1].setAttribute("stop-color",`hsl(${360*o} 100% 50% / ${i})`),this.#q||this.#R.setAttribute("transform",`translate(${64*i}, 0)`),this.#G.children[0].setAttribute("stop-color",`hsl(${360*o} ${100*r}% ${100*s}% / 0)`),this.#G.children[1].setAttribute("stop-color",`hsl(${360*o} ${100*r}% ${100*s}% / 1)`),this.#W||(this.#T.setAttribute("cx",""+64*e),this.#T.setAttribute("cy",""+48*(1-n)))}this.#Y=!1,this.#W=!1,this.#q=!1}setOptions(t){l(this.#u,t);const{converters:{to:e,from:n},alpha:i}=this.#u;return this.#B.style.display=i?"":"none",this.#K=i?t=>rt($t(t)):t=>it(Et(t)),this.#J=i?t=>function([t,e,n,i]){return[...Vt([t,e,n]),i]}(ot(t)):t=>Vt(nt(t)),this.#$=e,this.#C=n,this}}class le extends D{#X;#Z;#b;#Q;#u={open:!1};constructor(t,n,i={}){super(t,n,"muigui-pop-down-controller"),this.#X=this.add(new Kt("div","muigui-pop-down-top"));const o=this.#X.addElem(e("input",{type:"checkbox",onChange:()=>{this.#u.open=o.checked,this.updateDisplay()}}));this.#b=o,this.#Z=this.#X.add(new Kt("div","muigui-pop-down-values")),this.#Q=this.add(new Kt("div","muigui-pop-down-bottom")),this.setOptions(i)}setKnobColor(t){this.#b&&(this.#b.style=`\n --range-color: ${t};\n --value-bg-color: ${t};\n `)}updateDisplay(){super.updateDisplay();const{open:t}=this.#u;this.domElement.children[1].classList.toggle("muigui-open",t),this.domElement.children[1].classList.toggle("muigui-closed",!t)}setOptions(t){l(this.#u,t),super.setOptions(t),this.updateDisplay()}addTop(t){return this.#Z.add(t)}addBottom(t){return this.#Q.add(t)}}class ue extends le{#A;#S;#$;constructor(t,e,n={}){super(t,e,"muigui-color-chooser");const i=n.format||St(this.getValue()),{color:o,text:r}=qt[i];this.#$=o.to,this.#S=new G(this,{converters:r,alpha:It(i)}),this.#A=new ae(this,{converters:o,alpha:It(i)}),this.addTop(this.#S),this.addBottom(this.#A),this.__setKnobHelper=()=>{if(this.#$){const t=this.#$(this.getValue()),e=yt(Z(t));e[2]=(e[2]+50)%100;const n=Q(vt(e));this.setKnobColor(`${t.substring(0,7)}FF`,n)}},this.updateDisplay()}updateDisplay(){super.updateDisplay(),this.__setKnobHelper&&this.__setKnobHelper()}setOptions(t){return super.setOptions(t),this}}class ce extends ee{add(t,e,...n){const i=t instanceof g?t:function(t,e,...n){const[i]=n;if(Array.isArray(i))return new z(t,e,{keyValues:i});const o=typeof t[e];switch(o){case"number":if("number"==typeof n[0]&&"number"==typeof n[1]){const i=n[0],o=n[1],r=n[2];return new B(t,e,{min:i,max:o,...r&&{step:r}})}return 0===n.length?new j(t,e,...n):new B(t,e,...n);case"boolean":return new N(t,e,...n);case"function":return new f(t,e,...n);case"string":return new R(t,e,...n);case"undefined":throw new Error(`no property named ${e}`);default:throw new Error(`unhandled type ${o} for property ${e}`)}}(t,e,...n);return this.addController(i)}addCanvas(t){return this.addController(new Jt(t))}addColor(t,e,n={}){const i=t[e];return It(n.format||St(i))?this.addController(new ue(t,e,n)):this.addController(new Zt(t,e,n))}addDivider(){return this.addController(new Qt)}addFolder(t){return this.addController(new ce(t))}addLabel(t){return this.addController(new ne(t))}}class he extends HTMLElement{constructor(){super(),this.shadow=this.attachShadow({mode:"open"})}}customElements.define("muigui-element",he);const de=new CSSStyleSheet;de.replaceSync(t.default);const pe=new CSSStyleSheet;function me(t){let e,n;function i(){if(e&&!n){const o=e;e=void 0,n=t.replace(o).then((()=>{n=void 0,i()}))}}return function(t){e=t,i()}}const ge=me(de),fe=me(pe);class be extends ce{static converters=U;static mapRange=u;static makeRangeConverters=c;static makeRangeOptions=h;static makeMinMaxPair=p;#tt=new CSSStyleSheet;constructor(t={}){super("Controls","muigui-root"),t instanceof HTMLElement&&(t={parent:t});const{autoPlace:n=!0,width:i,title:o="Controls"}=t;let{parent:r}=t;if(i&&(this.domElement.style.width=/^\d+$/.test(i)?`${i}px`:i),void 0===r&&n&&(r=document.body,this.domElement.classList.add("muigui-auto-place")),r){const t=e("muigui-element");t.shadowRoot.adoptedStyleSheets=[de,pe,this.#tt],t.shadow.appendChild(this.domElement),r.appendChild(t)}o&&this.title(o),this.domElement.classList.add("muigui","muigui-colors")}setStyle(t){this.#tt.replace(t)}static setBaseStyles(t){ge(t)}static getBaseStyleSheet(){return de}static setUserStyles(t){fe(t)}static getUserStyleSheet(){return pe}static setTheme(e){be.setBaseStyles(`${t.default}\n${t.themes[e]||""}`)}}function ve(){}const xe={ArrowLeft:[-1,0],ArrowRight:[1,0],ArrowUp:[0,-1],ArrowDown:[0,1]};function we(t,{onDown:e=ve,onUp:n=ve}){const i=function(t){const i=t.shiftKey?10:1,[o,r]=(xe[t.key]||[0,0]).map((t=>t*i));("keydown"===t.type?e:n)({type:t.type.substring(3),dx:o,dy:r,event:t})};return t.addEventListener("keydown",i),t.addEventListener("keyup",i),function(){t.removeEventListener("keydown",i),t.removeEventListener("keyup",i)}}function ye(t,e=""){if(!t)throw new Error(e)}function ke(t,e,n,i,o,r){const s=Math.abs(n)*Math.cos(r),a=Math.abs(i)*Math.sin(r);return[t+Math.cos(o)*s-Math.sin(o)*a,e+Math.sin(o)*s+Math.cos(o)*a]}function Ee(t,e,n,i,o){ye(Math.abs(i-o)<=2*Math.PI),ye(i>=-Math.PI&&i<=2*Math.PI),ye(i<=o),ye(o>=-Math.PI&&o<=4*Math.PI);const{x1:r,y1:s,x2:a,y2:l,fa:u,fs:c}=function(t,e,n,i,o,r,s){const[a,l]=ke(t,e,n,i,o,r),[u,c]=ke(t,e,n,i,o,r+s);return{x1:a,y1:l,x2:u,y2:c,fa:Math.abs(s)>Math.PI?1:0,fs:s>0?1:0}}(t,e,n,n,0,i,o-i);return Math.abs(Math.abs(i-o)-2*Math.PI)>Number.EPSILON?`M${t} ${e} L${r} ${s} A ${n} ${n} 0 ${u} ${c} ${a} ${l} L${t} ${e}`:`M${r} ${s} L${r} ${s} A ${n} ${n} 0 ${u} ${c} ${a} ${l}`}const $e=t=>a(t+Math.PI,2*Math.PI)-Math.PI;class Ce extends v{#et;#nt;#it;#ot;#u={step:1,min:-180,max:180,dirMin:-Math.PI,dirMax:Math.PI,wrap:void 0,converters:F};constructor(t,n={}){const i=L();super(e("div",{className:"muigui-direction muigui-no-scroll",innerHTML:'\n\n',onWheel:e=>{e.preventDefault();const{min:n,max:r,step:l}=this.#u,u=i(e,l);let c=this.#it+u;this.#ot&&(c=a(c-n,r-n)+n);const h=o(s(c,(t=>t),l),n,r);t.setValue(h)}}));const r=e=>{const{min:n,max:i,step:r,dirMin:a,dirMax:l}=this.#u,u=2*e.nx-1,c=2*e.ny-1,h=Math.atan2(c,u),d=(a+l)/2,p=o(($e(h-d)-$e(a-d))/(l-a),0,1),m=s(n+(i-n)*p,(t=>t),r);t.setValue(m)};re(this.domElement,{onDown:r,onMove:r}),we(this.domElement,{onDown:e=>{const{min:n,max:i,step:r}=this.#u,a=o(s(this.#it+e.dx*r,(t=>t),r),n,i);t.setValue(a)}}),this.#et=this.$("#muigui-arrow"),this.#nt=this.$("#muigui-range"),this.setOptions(n)}updateDisplay(t){this.#it=t;const{min:e,max:n}=this.#u,i=(t-e)/(n-e),o=(r=this.#u.dirMin,s=this.#u.dirMax,r+(s-r)*i);var r,s;this.#et.style.transform=`rotate(${o}rad)`}setOptions(t){l(this.#u,t);const{dirMin:e,dirMax:n,wrap:i}=this.#u;this.#ot=void 0!==i?i:Math.abs(e-n)>=2*Math.PI-Number.EPSILON;const[o,r]=e(o.push(i),e("label",{},[e("input",{type:"radio",name:r,value:a,onChange:function(){this.checked&&t.setFinalValue(s.#D[this.value])}}),e("button",{type:"button",textContent:n,onClick:function(){this.previousElementSibling.click()}})]))))));const s=this;this.#D=o,this.cols(i)}updateDisplay(t){const e=this.#D.indexOf(t);for(let t=0;t{e({rect:t.getBoundingClientRect(),elem:t})})).observe(t)}function De(t,e,n,i){Se(t,(({rect:o})=>{const{width:r,height:s}=o;t.setAttribute("viewBox",`-${r*e} -${s*n} ${r} ${s}`),i({elem:t,rect:o})}))}function Ne(t,e,n,i,o,r){const a=[];tt),n)),e=Math.min(e,o);for(let i=t;i<=e;i+=n)a.push(`M${i} 0 l0 ${r}`);return a.join(" ")}class Fe extends v{#rt;#st;#at;#lt;#ut;#ct;#ht;#dt;#pt;#it;#mt;#u={min:-100,max:100,step:1,unit:10,unitSize:10,ticksPerUnit:5,labelFn:t=>t,tickHeight:1,limits:!0,thicksColor:void 0,orientation:void 0};constructor(t,n){const i=L();let r;super(e("div",{innerHTML:'\n\n',className:"muigui-no-v-scroll",onWheel:e=>{e.preventDefault();const{min:n,max:r,step:a}=this.#u,l=i(e,a),u=o(s(this.#it+l,(t=>t),a),n,r);t.setValue(u)}})),this.#rt=this.$("svg"),this.#st=this.$("#muigui-origin"),this.#at=this.$("#muigui-ticks"),this.#lt=this.$("#muigui-thicks"),this.#ut=this.$("#muigui-numbers"),this.#ct=this.$("#muigui-left-grad"),this.#ht=this.$("#muigui-right-grad"),this.setOptions(n),re(this.domElement,{onDown:()=>{r=this.#it},onMove:e=>{const{min:n,max:i,unitSize:a,unit:l,step:u}=this.#u,c=o(s(r-e.dx/a*l,(t=>t),u),n,i);t.setValue(c)}}),we(this.domElement,{onDown:e=>{const{min:n,max:i,step:r}=this.#u,a=o(s(this.#it+e.dx*r,(t=>t),r),n,i);t.setValue(a)}}),De(this.#rt,.5,0,(({rect:{width:t}})=>{this.#ct.setAttribute("x",-t/2),this.#ht.setAttribute("x",t/2-20),this.#mt=function(t){const e=t.innerHTML;t.innerHTML="- ";const n=t.querySelector("text").getComputedTextLength();return t.innerHTML=e,n}(this.#ut),this.#dt=t,this.#gt()}))}#gt(){if(!this.#dt||void 0===this.#it)return;const{labelFn:t,limits:e,min:n,max:i,orientation:o,tickHeight:r,ticksPerUnit:a,unit:l,unitSize:u,thicksColor:c}=this.#u,h=Math.ceil(this.#dt/u),d=this.#it/l,p=Math.round(d-h),m=p*u,g=(p+2*h)*u,f=e?n*u/l:m,b=e?i*u/l:g,v=""===t(1)?10:5;a>1&&this.#at.setAttribute("d",Ne(m,g,u/a,f,b,v*r)),this.#lt.style.stroke=c,this.#lt.setAttribute("d",Ne(m,g,u,f,b,v)),this.#ut.innerHTML=function(t,e,n,i,o,r,a,l){const u=[];tt),n)),e=Math.min(e,a);const c=Math.max(0,-Math.log10(i));for(let r=t;r<=e;r+=n)u.push(`${h=r/n*i,l(h.toFixed(c))}`);var h;return u.join("\n")}(m,g,u,l,this.#mt,f,b,t),this.#st.setAttribute("transform",`translate(${-this.#it*u/l} 0)`),this.#rt.classList.toggle("muigui-slider-up","up"===o)}updateDisplay(t){this.#it=t,this.#gt()}setOptions(t){return l(this.#u,t),this}}class Ae extends D{constructor(t,e,n={}){super(t,e,"muigui-slider"),this.add(new Fe(this,n)),this.add(new O(this,n)),this.updateDisplay()}}class Ue extends v{#rt;#et;#T;#it=[];constructor(t){super(e("div",{innerHTML:'\n\n',className:"muigui-no-scroll"}));const n=e=>{const{width:n,height:i}=this.#rt.getBoundingClientRect(),o=2*e.nx-1,r=2*e.ny-1;t.setValue([o*n*.5,r*i*.5])};re(this.domElement,{onDown:n,onMove:n}),this.#rt=this.$("svg"),this.#et=this.$("#muigui-arrow"),this.#T=this.$("#muigui-circle"),De(this.#rt,.5,.5,(()=>this.#ft))}#ft(){const[t,e]=this.#it;this.#et.setAttribute("d",`M0,0L${t},${e}`),this.#T.setAttribute("transform",`translate(${t}, ${e})`)}updateDisplay(t){this.#it[0]=t[0],this.#it[1]=t[1],this.#ft()}}class Le extends le{constructor(t,e){super(t,e,"muigui-vec2");const n=t=>({setValue:e=>{const n=this.getValue();n[t]=e,this.setValue(n)},setFinalValue:e=>{const n=this.getValue();n[t]=e,this.setFinalValue(n)}});this.addTop(new O(n(0),{converters:{to:t=>t[0],from:A.from}})),this.addTop(new O(n(1),{converters:{to:t=>t[1],from:A.from}})),this.addBottom(new Ue(this)),this.updateDisplay()}}export{ue as ColorChooser,Ve as Direction,Me as RadioGrid,B as Range,z as Select,Ae as Slider,j as TextNumber,Le as Vec2,be as default};
-//# sourceMappingURL=muigui.module.min.js.map
diff --git a/package-lock.json b/package-lock.json
index f14f763..22931c3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"rollup": "^3.20.2",
"servez": "^2.1.2",
"tslib": "^2.6.2",
- "typescript": "^5.3.2"
+ "typescript": "5.2"
}
},
"node_modules/@babel/code-frame": {
@@ -4000,9 +4000,9 @@
}
},
"node_modules/typescript": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
- "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -7159,9 +7159,9 @@
}
},
"typescript": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
- "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+ "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true
},
"unbzip2-stream": {
diff --git a/package.json b/package.json
index 7168ce7..db52dde 100644
--- a/package.json
+++ b/package.json
@@ -6,15 +6,17 @@
"module": "src/muigui.js",
"type": "module",
"scripts": {
- "start": "node build/serve.js",
- "watch": "npm run start",
"build": "npm run build-normal",
+ "build-ci": "npm run build && node build/prep-for-deploy.js",
"build-normal": "rollup -c",
"build-min": "rollup -c",
+ "check-ci": "npm run pre-push",
"eslint": "eslint \"**/*.js\"",
"fix": "eslint --fix",
"pre-push": "npm run eslint && npm run test",
- "test": "node test/puppeteer.js"
+ "start": "node build/serve.js",
+ "test": "node test/puppeteer.js",
+ "watch": "npm run start"
},
"repository": {
"type": "git",
@@ -52,6 +54,6 @@
"rollup": "^3.20.2",
"servez": "^2.1.2",
"tslib": "^2.6.2",
- "typescript": "^5.3.2"
+ "typescript": "5.2"
}
}