super-three 0.136.1 → 0.137.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +1 -1
  2. package/build/three.cjs +37132 -0
  3. package/build/three.js +71 -53
  4. package/build/three.min.js +2 -2
  5. package/build/three.module.js +80 -47
  6. package/examples/js/animation/MMDAnimationHelper.js +3 -1
  7. package/examples/js/controls/experimental/CameraControls.js +6 -2
  8. package/examples/js/exporters/GLTFExporter.js +82 -39
  9. package/examples/js/geometries/TextGeometry.js +12 -10
  10. package/examples/js/interactive/HTMLMesh.js +14 -2
  11. package/examples/js/loaders/EXRLoader.js +20 -9
  12. package/examples/js/loaders/GLTFLoader.js +1 -1
  13. package/examples/js/loaders/LDrawLoader.js +541 -425
  14. package/examples/js/loaders/MD2Loader.js +5 -5
  15. package/examples/js/loaders/MTLLoader.js +10 -3
  16. package/examples/js/loaders/PCDLoader.js +2 -2
  17. package/examples/js/loaders/STLLoader.js +1 -1
  18. package/examples/js/loaders/SVGLoader.js +3 -2
  19. package/examples/js/modifiers/EdgeSplitModifier.js +0 -6
  20. package/examples/js/modifiers/SimplifyModifier.js +0 -10
  21. package/examples/js/objects/Lensflare.js +2 -2
  22. package/examples/js/postprocessing/OutlinePass.js +1 -5
  23. package/examples/js/renderers/CSS2DRenderer.js +25 -19
  24. package/examples/js/renderers/CSS3DRenderer.js +37 -32
  25. package/examples/js/utils/LDrawUtils.js +182 -0
  26. package/examples/jsm/exporters/GLTFExporter.js +80 -12
  27. package/examples/jsm/geometries/TextGeometry.js +13 -13
  28. package/examples/jsm/interactive/HTMLMesh.js +16 -2
  29. package/examples/jsm/libs/flow.module.js +930 -169
  30. package/examples/jsm/loaders/EXRLoader.js +19 -9
  31. package/examples/jsm/loaders/GLTFLoader.js +1 -1
  32. package/examples/jsm/loaders/LDrawLoader.js +534 -442
  33. package/examples/jsm/loaders/MD2Loader.js +5 -5
  34. package/examples/jsm/loaders/MTLLoader.js +11 -4
  35. package/examples/jsm/loaders/PCDLoader.js +2 -2
  36. package/examples/jsm/loaders/STLLoader.js +1 -1
  37. package/examples/jsm/loaders/SVGLoader.js +3 -1
  38. package/examples/jsm/loaders/VRMLLoader.js +0 -1
  39. package/examples/jsm/modifiers/EdgeSplitModifier.js +0 -6
  40. package/examples/jsm/modifiers/SimplifyModifier.js +0 -10
  41. package/examples/jsm/node-editor/NodeEditor.js +435 -181
  42. package/examples/jsm/node-editor/accessors/NormalEditor.js +8 -8
  43. package/examples/jsm/node-editor/accessors/PositionEditor.js +8 -8
  44. package/examples/jsm/node-editor/accessors/UVEditor.js +6 -7
  45. package/examples/jsm/node-editor/core/BaseNode.js +83 -0
  46. package/examples/jsm/node-editor/display/BlendEditor.js +7 -6
  47. package/examples/jsm/node-editor/examples/animate-uv.json +1 -1
  48. package/examples/jsm/node-editor/examples/fake-top-light.json +1 -1
  49. package/examples/jsm/node-editor/examples/oscillator-color.json +1 -1
  50. package/examples/jsm/node-editor/examples/rim.json +1 -1
  51. package/examples/jsm/node-editor/inputs/ColorEditor.js +14 -10
  52. package/examples/jsm/node-editor/inputs/FloatEditor.js +6 -7
  53. package/examples/jsm/node-editor/inputs/SliderEditor.js +4 -5
  54. package/examples/jsm/node-editor/inputs/Vector2Editor.js +6 -7
  55. package/examples/jsm/node-editor/inputs/Vector3Editor.js +8 -9
  56. package/examples/jsm/node-editor/inputs/Vector4Editor.js +9 -10
  57. package/examples/jsm/node-editor/materials/BasicMaterialEditor.js +87 -0
  58. package/examples/jsm/node-editor/materials/PointsMaterialEditor.js +97 -0
  59. package/examples/jsm/node-editor/materials/StandardMaterialEditor.js +20 -17
  60. package/examples/jsm/node-editor/math/AngleEditor.js +39 -0
  61. package/examples/jsm/node-editor/math/DotEditor.js +6 -7
  62. package/examples/jsm/node-editor/math/InvertEditor.js +4 -3
  63. package/examples/jsm/node-editor/math/LimiterEditor.js +11 -10
  64. package/examples/jsm/node-editor/math/NormalizeEditor.js +6 -5
  65. package/examples/jsm/node-editor/math/OperatorEditor.js +12 -11
  66. package/examples/jsm/node-editor/math/PowerEditor.js +6 -5
  67. package/examples/jsm/node-editor/math/TrigonometryEditor.js +12 -7
  68. package/examples/jsm/node-editor/procedural/CheckerEditor.js +4 -3
  69. package/examples/jsm/node-editor/scene/MeshEditor.js +99 -0
  70. package/examples/jsm/node-editor/scene/Object3DEditor.js +160 -0
  71. package/examples/jsm/node-editor/scene/PointsEditor.js +99 -0
  72. package/examples/jsm/node-editor/utils/JoinEditor.js +58 -0
  73. package/examples/jsm/node-editor/utils/OscillatorEditor.js +7 -6
  74. package/examples/jsm/node-editor/utils/SplitEditor.js +39 -0
  75. package/examples/jsm/node-editor/utils/TimerEditor.js +6 -5
  76. package/examples/jsm/postprocessing/OutlinePass.js +1 -4
  77. package/examples/jsm/renderers/CSS2DRenderer.js +24 -19
  78. package/examples/jsm/renderers/CSS3DRenderer.js +36 -30
  79. package/examples/jsm/renderers/nodes/accessors/UVNode.js +4 -4
  80. package/examples/jsm/renderers/nodes/core/NodeBuilder.js +2 -1
  81. package/examples/jsm/renderers/nodes/utils/SplitNode.js +19 -4
  82. package/examples/jsm/utils/LDrawUtils.js +18 -11
  83. package/package.json +16 -9
  84. package/src/constants.js +1 -3
  85. package/src/core/BufferGeometry.js +2 -2
  86. package/src/geometries/LatheGeometry.js +1 -1
  87. package/src/loaders/FileLoader.js +20 -3
  88. package/src/math/Box3.js +24 -10
  89. package/src/renderers/WebGLRenderTarget.js +4 -1
  90. package/src/renderers/WebGLRenderer.js +3 -3
  91. package/src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl.js +1 -1
  92. package/src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl.js +2 -2
  93. package/src/renderers/webgl/WebGLBackground.js +2 -2
  94. package/src/renderers/webgl/WebGLGeometries.js +2 -2
  95. package/src/renderers/webgl/WebGLPrograms.js +2 -2
  96. package/src/renderers/webgl/WebGLState.js +2 -2
  97. package/src/renderers/webgl/WebGLTextures.js +9 -6
  98. package/src/renderers/webgl/WebGLUtils.js +1 -3
  99. package/src/renderers/webxr/WebXRManager.js +2 -0
  100. package/src/utils.js +15 -1
@@ -16,7 +16,7 @@ function __flow__addCSS( css ) {
16
16
 
17
17
  }
18
18
 
19
- __flow__addCSS( `@keyframes f-animation-open { 0% { transform: scale(.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; }}f-canvas,f-canvas canvas { position: absolute; top: 0; left: 0; margin: 0; padding: 0; width: 100%; height: 100%; -webkit-touch-callout: none; }f-canvas { overflow: auto; cursor: grab;}f-canvas canvas.front { z-index: 10;}body.dragging f-canvas,body.connecting f-canvas { overflow: hidden !important;}body.dragging *:not(.drag) { pointer-events: none !important;}f-canvas.grabbing * { cursor: grabbing; user-select: none;}f-canvas canvas { position: fixed; overflow: hidden; pointer-events: none;}f-canvas::-webkit-scrollbar { width: 8px; height: 8px;}f-canvas::-webkit-scrollbar-thumb:hover{ background: #014fc5;}f-canvas::-webkit-scrollbar-track { background: #363636;}f-canvas::-webkit-scrollbar-thumb { background-color: #666666; border-radius: 10px; border: 0;}f-canvas f-content,f-canvas f-area { position: absolute; display: block;}f-node { position: absolute; margin: 0; padding: 0; user-select: none; width: 320px; z-index: 1; cursor: auto; filter: drop-shadow(0 0 10px #00000061); backdrop-filter: blur(4px);}f-node.selected { z-index: 2;}f-node.selected,f-canvas.dragging-rio f-node:hover,f-canvas.dragging-lio f-node:hover { filter: drop-shadow(0 0 10px #00000061) drop-shadow(0 0 8px #4444dd);}f-node.closed f-element:not(:first-child) { display: none;}f-node.center { top: 50%; left: 50%; transform: translate( -50%, -50% );}f-node.top-right { top: 0; right: 0;}f-node.top-center { top: 0; left: 50%; transform: translateX( -50% );}f-node.top-left { top: 0; left: 0;}f-node { transition: filter 0.2s ease;}f-node { animation: .2s f-animation-open 1 alternate ease-out;}f-drop,f-menu,f-menu button,f-element,f-element input,f-element select,f-element button,f-element textarea { font-family: 'Open Sans', sans-serif; font-size: 13px; text-transform: capitalize; color: #eeeeee; outline: solid 0px #000; letter-spacing: .2px; margin: 0; padding: 0; border: 0; user-select: none; -webkit-tap-highlight-color: transparent; transition: background 0.2s ease;}f-element input { transition: background 0.1s ease;}f-element input,f-element select,f-element button,f-element textarea { background-color: #242427;}f-element { position: relative; width: calc( 100% - 14px ); background: rgba(45, 45, 48, 0.95); pointer-events: auto; border-bottom: 2px solid #232323; display: flex; padding-left: 7px; padding-right: 7px; padding-top: 2px; padding-bottom: 2px;}f-element { height: 24px;}f-element input { margin-top: 2px; margin-bottom: 2px; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%); margin-left: 2px; margin-right: 2px; width: 100%; padding-left: 4px; padding-right: 4px;}f-element input.number { cursor: col-resize;}f-element input:focus[type='text'], f-element input:focus[type='range'], f-element input:focus[type='color'] { background: rgba( 0, 0, 0, 0.6 ); outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-element input[type='color'] { appearance: none; padding: 0; margin-left: 2px; margin-right: 2px; height: calc( 100% - 4px ); margin-top: 2px; border: none; }f-element input[type='color']::-webkit-color-swatch-wrapper { padding: 2px;}f-element input[type='color']::-webkit-color-swatch { border: none; cursor: alias;}f-element input[type='range'] { appearance: none; width: 100%; overflow: hidden; padding: 0; cursor: ew-resize;}f-element input[type='range']::-webkit-slider-runnable-track { appearance: none; height: 10px; color: #13bba4; margin: 0;}f-element input[type='range']::-webkit-slider-thumb { appearance: none; width: 0; background: #434343; box-shadow: -500px 0 0 500px rgba( 0, 120, 255, 0.98 ); border-radius: 50%; border: 0 !important;}f-element input[type='range']::-webkit-slider-runnable-track { margin-left: -4px; margin-right: -5px;}f-element input[type='checkbox'] { appearance: none; cursor: pointer;}f-element input[type='checkbox'].toggle { height: 20px; width: 45px; border-radius: 16px; display: inline-block; position: relative; margin: 0; margin-top: 2px; background: linear-gradient( 0deg, #292929 0%, #0a0a0ac2 100% ); transition: all 0.2s ease;}f-element input[type='checkbox'].toggle:after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: white; box-shadow: 0 1px 2px rgba(44, 44, 44, 0.2); transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);}f-element input[type='checkbox'].toggle:checked { background: linear-gradient( 0deg, #0177fb 0%, #0177fb 100% );}f-element input[type='checkbox'].toggle:checked:after { transform: translatex(25px);}f-element.auto-height { display: table;}f-element textarea { width: calc( 100% - 18px ); padding-top: 1px; padding-bottom: 3px; padding-left: 3px; padding-right: 8px; margin-top: 2px; margin-left: 2px; height: calc( 100% - 8px ); max-height: 300px; border-radius: 2px; resize: none; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element.auto-height textarea { resize: auto;}f-element select { width: 100%; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; padding-left: 5px; cursor: pointer; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element f-toolbar { position: absolute; display: flex; top: 0; width: 100%; height: 100%; align-content: space-around;}f-element.output-right f-toolbar { right: 7px; float: right; justify-content: end;}f-element f-toolbar { margin-top: auto; margin-bottom: auto; margin-left: 3px; margin-right: 3px; font-size: 18px; line-height: 18px;}f-element f-toolbar button { opacity: .7; cursor: pointer; font-size: 14px; width: unset; height: unset; border-radius: unset; border: unset; outline: 0; background-color: unset; box-shadow: unset;}f-element f-toolbar button:hover,f-element f-toolbar button:active { opacity: 1; border: 0; background-color: unset;}f-element input.range-value { width: 60px; text-align: center;}f-menu.context button,f-element button { width: 100%; height: calc( 100% - 4px ); margin-left: 2px; margin-right: 2px; margin-top: 2px; border-radius: 3px; cursor: pointer;}f-element button { box-shadow: inset 1px 1px 1px 0 rgb(255 255 255 / 17%), inset -2px -2px 2px 0 rgb(0 0 0 / 26%);}f-element button:hover { color: #fff; background-color: #2a2a2a;}f-element button:active { border: 1px solid rgba( 0, 120, 255, 0.98 );}f-element f-inputs,f-element f-subinputs { display: flex; width: 100%;}f-element f-inputs { left: 100px; top: 50%; transform: translateY(-50%); position: absolute; width: calc( 100% - 106px ); height: calc( 100% - 4px ); z-index: 1;}f-element.inputs-disable f-inputs { filter: grayscale(100%); opacity: .5;}f-element.inputs-disable f-inputs input { pointer-events: none;}f-element f-label,f-element span { margin: auto; text-shadow: 1px 1px 0px #0007;}f-element f-label { padding-left: 4px; white-space: nowrap; position: absolute; top: 50%; transform: translateY(-50%); width: calc( 100% - 20px );}f-element.right f-label { text-align: right;}f-element f-label i { float: left; font-size: 18px; margin-right: 6px;}f-element f-label.center { width: 100%; text-align: center; display: block;}f-element.title { height: 29px; background-color: #3a3a3ab0; background-color: #3b3b43ed; cursor: all-scroll; border-top-left-radius: 6px; border-top-right-radius: 6px;}f-element.blue { background-color: #014fc5;}f-element.red { background-color: #bd0b0b;}f-element.green { background-color: #148d05;}f-element.yellow { background-color: #d6b100;}f-element.title.left { text-align: left; display: inline-grid; justify-content: start;}f-element.title span { text-align: center; font-size: 15px; padding-top: 2px;}f-element.title i { font-size: 18px; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: .5;}f-element.output-right.title i { left: 10px; right: unset;}f-element.title.left span { text-align: left;}f-element f-io { border: 2px solid #dadada; width: 7px; height: 7px; position: absolute; background: #242427; border-radius: 8px; float: left; left: -7px; top: calc( 50% - 5px ); cursor: alias; box-shadow: 0 0 3px 2px #0000005e; z-index: 1;}f-element f-io.connect,f-canvas.dragging-rio f-element:hover f-io.lio,f-canvas.dragging-lio f-element:hover f-io.rio { border: 2px solid #0177fb; zoom: 1.4;}f-node.io-connect f-io:not(.connect) { border: 2px solid #dadada !important; zoom: 1 !important;}f-element f-io.rio { float: right; right: -7px; left: unset;}f-element f-disconnect { position: absolute; left: -35px; top: 50%; font-size: 22px; transform: translateY( -50% ); filter: drop-shadow(0 0 5px #000); text-shadow: 0px 0px 5px black; cursor: pointer;}f-element.output-right f-disconnect { right: -35px; left: unset;}f-element f-disconnect:hover { color: #ff3300;}f-element textarea::-webkit-scrollbar { width: 6px;}f-element textarea::-webkit-scrollbar-track { background: #111; } f-element textarea::-webkit-scrollbar-thumb { background: #0177fb; }f-element textarea::-webkit-scrollbar-thumb:hover { background: #1187ff; }f-element.small { height: 18px;}f-element.large { height: 36px;}body.connecting f-node:not(.io-connect) f-element:hover,f-element.select { background-color: rgba(61, 70, 82, 0.98);}f-drop { width: 100%; height: 100%; position: sticky; left: 0; top: 0; background: #02358417; text-align: center; justify-content: center; align-items: center; display: flex; box-shadow: inset 0 0 20px 10px #464ace17; pointer-events: none; transition: all .07s; opacity: 0; visibility: hidden;}f-drop.visible { visibility: unset; opacity: unset; transition: all .23s;}f-drop span { opacity: .5; font-size: 40px; text-shadow: 0px 0px 5px #000; font-weight: bold;}f-tooltip { pointer-events: none;}f-tooltip { position: absolute; left: 0; top: 0; background: rgba(0,0,0,.8); backdrop-filter: blur(4px); font-size: 14px; padding: 7px; border-radius: 10px; top: 50%; transform: translateY(-50%); visibility: hidden; pointer-events: none; opacity: 0; transition: all 0.3s ease; z-index: 150; white-space: nowrap;}f-menu.context { position: absolute; width: 170px; padding: 2px; margin: 0; background: #17171794; z-index: 110; font-size: 12px; border-radius: 6px; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; box-shadow: 3px 3px 6px rgba(0,0,0,.2); transition: opacity 0.2s ease, transform 0.1s ease;}f-menu.context.hidden { visibility: hidden; opacity: 0;}f-menu.context f-item { display: block; position: relative; margin: 0; padding: 0; white-space: nowrap;}f-menu.context f-item.submenu::after { content: ""; position: absolute; right: 6px; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); border: 5px solid transparent; border-left-color: #808080;}f-menu.context f-item:hover > f-menu,f-menu.context f-item.active > f-menu { visibility: unset; transform: unset; opacity: unset;}f-menu.context f-menu { top: 0px; left: calc( 100% - 4px );}f-menu.context f-item button { overflow: visible; display: block; width: calc( 100% - 6px ); text-align: left; cursor: pointer; white-space: nowrap; padding: 6px 8px; border-radius: 3px; background: #2d2d32; border: 0; color: #ddd; margin: 3px; text-shadow: 1px 1px 0px #0007;}f-menu.context f-item button i { float: left; font-size: 16px;}f-menu.context f-item button span { margin-left: 6px;}f-menu.context f-item:hover > button,f-menu.context f-item.active > button { color: #fff; background: #313136;}f-menu.context f-item button:active { outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-menu.context f-item f-tooltip { margin-left: 120px;}f-menu.circle { position: absolute; left: 30px; top: 30px; z-index: 100;}f-menu.circle f-item { display: flex; justify-content: end; align-content: space-around; margin-bottom: 12px;}f-menu.circle f-item button { width: 50px; height: 50px; font-size: 26px; background: #17171794; border-radius: 50%; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; line-height: 100%; cursor: pointer; box-shadow: 3px 3px 6px rgba(0,0,0,.2);}f-menu.circle f-item f-tooltip { margin-left: 50px;}.f-rounded f-node f-element,.f-rounded f-node f-element.title.left { border-radius: 10px 5px 10px 5px;}.f-rounded f-node f-element input, .f-rounded f-node f-element select,.f-rounded f-node f-element button,.f-rounded f-node f-element textarea,.f-rounded f-node f-element input[type='checkbox'].toggle,.f-rounded f-node f-element input[type='checkbox'].toggle:after { border-radius: 20px 10px;}.f-rounded f-node f-element input { padding-left: 7px; padding-right: 7px;}.f-rounded f-menu.context,.f-rounded f-menu.context f-item button { border-radius: 20px 10px;}@media (hover: hover) and (pointer: fine) { f-node:not(.selected):hover { filter: drop-shadow(0 0 6px #66666630); } f-element f-toolbar { visibility: hidden; opacity: 0; transition: opacity 0.2s ease; } f-node:hover > f-element f-toolbar { visibility: visible; opacity: 1; } f-element f-io:hover { border: 2px solid #0177fb; zoom: 1.4; } f-menu.circle f-item button:hover { background-color: #2a2a2a; } f-menu.circle f-item button:hover > f-tooltip, f-menu.context f-item button:hover > f-tooltip { visibility: visible; transform: translate(10px, -50%); opacity: 1; } f-menu.circle f-item button:focus > f-tooltip, f-menu.context f-item button:focus > f-tooltip { visibility: hidden; opacity: 0; }}f-canvas { will-change: top, left;}f-node { will-change: transform !important;}` );
19
+ __flow__addCSS( `@keyframes f-animation-open { 0% { transform: scale(.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; }}f-canvas,f-canvas canvas { position: absolute; top: 0; left: 0; margin: 0; padding: 0; width: 100%; height: 100%; -webkit-touch-callout: none; }f-canvas { overflow: auto; cursor: grab;}f-canvas canvas.front { z-index: 10;}body.dragging *:not(.drag) { pointer-events: none !important;}f-canvas.grabbing * { cursor: grabbing; user-select: none;}f-canvas canvas { position: fixed; overflow: hidden; pointer-events: none;}f-canvas::-webkit-scrollbar { width: 8px; height: 8px;}f-canvas::-webkit-scrollbar-thumb:hover{ background: #014fc5;}f-canvas::-webkit-scrollbar-track { background: #363636;}f-canvas::-webkit-scrollbar-thumb { background-color: #666666; border-radius: 10px; border: 0;}f-canvas f-content,f-canvas f-area { position: absolute; display: block;}f-node { position: absolute; margin: 0; padding: 0; user-select: none; width: 320px; z-index: 1; cursor: auto; filter: drop-shadow(0 0 10px #00000061); backdrop-filter: blur(4px);}f-node.selected { z-index: 2;}f-node.selected,f-canvas.dragging-rio f-node:hover,f-canvas.dragging-lio f-node:hover { filter: drop-shadow(0 0 10px #00000061) drop-shadow(0 0 8px #4444dd);}f-node.closed f-element:not(:first-child) { display: none;}f-node.center { top: 50%; left: 50%; transform: translate( -50%, -50% );}f-node.top-right { top: 0; right: 0;}f-node.top-center { top: 0; left: 50%; transform: translateX( -50% );}f-node.top-left { top: 0; left: 0;}f-node { transition: filter 0.2s ease;}f-node { animation: .2s f-animation-open 1 alternate ease-out;}f-tips,f-drop,f-menu,f-menu input,f-menu button,f-element,f-element input,f-element select,f-element button,f-element textarea { font-family: 'Open Sans', sans-serif; font-size: 13px; text-transform: capitalize; color: #eeeeee; outline: solid 0px #000; margin: 0; padding: 0; border: 0; user-select: none; -webkit-tap-highlight-color: transparent; transition: background 0.2s ease, filter 0.2s ease;}f-element input:read-only { color: #666;}f-element input,f-element textarea { text-transform: initial;}f-element input { transition: background 0.1s ease;}f-element input,f-element select,f-element button,f-element textarea { background-color: #232324d1;}f-element { position: relative; width: calc( 100% - 14px ); background: rgba(45, 45, 48, 0.95); pointer-events: auto; border-bottom: 2px solid #232323; display: flex; padding-left: 7px; padding-right: 7px; padding-top: 2px; padding-bottom: 2px;}f-element:after,f-element:before { transition: opacity .17s; opacity: 0; content: '';}f-element[tooltip]:hover:after,f-element[tooltip]:focus-within:after { font-size: 14px !important; display: flex; justify-content: center; position: fixed; margin-left: -7px; width: calc( 100% ); background: #1d1d1de8; border: 1px solid #444444a1; border-radius: 6px; color: #dadada; content: attr( tooltip ); margin-top: -41px; font-size: 16px; padding-top: 3px; padding-bottom: 3px; z-index: 10; opacity: 1; backdrop-filter: blur(4px); white-space: nowrap; overflow: hidden; text-shadow: 1px 1px 0px #0007;}f-element[tooltip]:hover:before,f-element[tooltip]:focus-within:before { border: solid; border-color: #1d1d1de8 transparent; border-width: 12px 6px 0 6px; left: calc( 50% - 6px ); bottom: 30px; position: absolute; opacity: 1; z-index: 11;}f-element[error] { background-color: #ff0000;}f-element[error]:hover:after,f-element[error]:focus-within:after { border: none; background-color: #ff0000bb; filter: drop-shadow( 2px 2px 5px #000 ); color: #fff;}f-element[error]:hover:before,f-element[error]:focus-within:before { border-color: #ff0000bb transparent;}f-element { height: 24px;}f-element input { margin-top: 2px; margin-bottom: 2px; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%); margin-left: 2px; margin-right: 2px; width: 100%; padding-left: 4px; padding-right: 4px;}f-element input.number { cursor: col-resize;}f-element input:focus[type='text'], f-element input:focus[type='range'], f-element input:focus[type='color'] { background: rgba( 0, 0, 0, 0.6 ); outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-element input[type='color'] { appearance: none; padding: 0; margin-left: 2px; margin-right: 2px; height: calc( 100% - 4px ); margin-top: 2px; border: none;}f-element input[type='color']::-webkit-color-swatch-wrapper { padding: 2px;}f-element input[type='color']::-webkit-color-swatch { border: none; cursor: alias;}f-element input[type='range'] { appearance: none; width: 100%; overflow: hidden; padding: 0; cursor: ew-resize;}f-element input[type='range']::-webkit-slider-runnable-track { appearance: none; height: 10px; color: #13bba4; margin: 0;}f-element input[type='range']::-webkit-slider-thumb { appearance: none; width: 0; background: #434343; box-shadow: -500px 0 0 500px rgba( 0, 120, 255, 0.98 ); border-radius: 50%; border: 0 !important;}f-element input[type='range']::-webkit-slider-runnable-track { margin-left: -4px; margin-right: -5px;}f-element input[type='checkbox'] { appearance: none; cursor: pointer;}f-element input[type='checkbox'].toggle { height: 20px; width: 45px; border-radius: 16px; display: inline-block; position: relative; margin: 0; margin-top: 2px; background: linear-gradient( 0deg, #292929 0%, #0a0a0ac2 100% ); transition: all 0.2s ease;}f-element input[type='checkbox'].toggle:after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: white; box-shadow: 0 1px 2px rgba(44, 44, 44, 0.2); transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);}f-element input[type='checkbox'].toggle:checked { background: linear-gradient( 0deg, #0177fb 0%, #0177fb 100% );}f-element input[type='checkbox'].toggle:checked:after { transform: translatex(25px);}f-element.auto-height { display: table;}f-element textarea { width: calc( 100% - 18px ); padding-top: 1px; padding-bottom: 3px; padding-left: 3px; padding-right: 8px; margin-top: 2px; margin-left: 2px; height: calc( 100% - 8px ); max-height: 300px; border-radius: 2px; resize: none; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element.auto-height textarea { resize: auto;}f-element select { width: 100%; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; cursor: pointer; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element f-toolbar { position: absolute; display: flex; top: 0; width: 100%; height: 100%; align-content: space-around;}f-element.input-right f-toolbar { right: 7px; float: right; justify-content: end;}f-element f-toolbar { margin-top: auto; margin-bottom: auto; margin-left: 3px; margin-right: 3px; font-size: 18px; line-height: 18px;}f-element f-toolbar button { opacity: .7; cursor: pointer; font-size: 14px; width: unset; height: unset; border-radius: unset; border: unset; outline: 0; background-color: unset; box-shadow: unset;}f-element f-toolbar button:hover,f-element f-toolbar button:active { opacity: 1; border: 0; background-color: unset;}f-element input.range-value { width: 60px; text-align: center;}f-menu.context button,f-element button { width: 100%; height: calc( 100% - 4px ); margin-left: 2px; margin-right: 2px; margin-top: 2px; border-radius: 3px; cursor: pointer;}f-element button { box-shadow: inset 1px 1px 1px 0 rgb(255 255 255 / 17%), inset -2px -2px 2px 0 rgb(0 0 0 / 26%);}f-element button:hover { color: #fff; background-color: #2a2a2a;}f-element button:active { border: 1px solid rgba( 0, 120, 255, 0.98 );}f-element f-inputs,f-element f-subinputs { display: flex; width: 100%;}f-element f-inputs { left: 100px; top: 50%; transform: translateY(-50%); position: absolute; width: calc( 100% - 106px ); height: calc( 100% - 4px ); z-index: 1;}f-element.inputs-disable f-inputs { filter: grayscale(100%); opacity: .5;}f-element.inputs-disable f-inputs input { pointer-events: none;}f-element f-label,f-element span { margin: auto; text-shadow: 1px 1px 0px #0007;}f-element f-label { padding-left: 4px; white-space: nowrap; position: absolute; top: 50%; transform: translateY(-50%); width: calc( 100% - 20px );}f-element.right f-label { text-align: right;}f-element.center f-label { text-align: center;}f-element f-label i { float: left; font-size: 18px; margin-right: 6px;}f-element f-label.center { width: 100%; text-align: center; display: block;}f-element.title { height: 29px; background-color: #3a3a3ab0; background-color: #3b3b43ed; cursor: all-scroll; border-top-left-radius: 6px; border-top-right-radius: 6px;}f-element.blue { background-color: #014fc5;}f-element.red { background-color: #bd0b0b;}f-element.green { background-color: #148d05;}f-element.yellow { background-color: #d6b100;}f-element.title.left { text-align: left; display: inline-grid; justify-content: start;}f-element.title span { text-align: center; font-size: 15px; padding-top: 2px;}f-element.title i { font-size: 18px; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: .5;}f-element.title f-toolbar i { font-size: 20px; right: unset; left: 0px;}f-element.input-right.title i { left: 10px; right: unset;}f-element.title.left span { text-align: left;}f-element f-io { border: 2px solid #dadada; width: 7px; height: 7px; position: absolute; background: #242427; border-radius: 8px; float: left; left: -7px; top: calc( 50% - 5px ); cursor: alias; box-shadow: 0 0 3px 2px #0000005e; z-index: 1;}f-element f-io.connect,f-canvas.dragging-rio f-element:hover f-io.lio,f-canvas.dragging-lio f-element:hover f-io.rio { zoom: 1.4;}f-node.io-connect f-io:not(.connect) { border: 2px solid #dadada !important; zoom: 1 !important;}f-element f-io.rio { float: right; right: -7px; left: unset;}f-element f-disconnect { position: absolute; left: -35px; top: 50%; font-size: 22px; transform: translateY( -50% ); filter: drop-shadow(0 0 5px #000); text-shadow: 0px 0px 5px black; cursor: pointer; transition: all .2s;}f-element.input-right f-disconnect { right: -35px; left: unset;}f-element f-disconnect:hover { color: #ff3300;}f-element textarea::-webkit-scrollbar { width: 6px;}f-element textarea::-webkit-scrollbar-track { background: #111; } f-element textarea::-webkit-scrollbar-thumb { background: #0177fb; }f-element textarea::-webkit-scrollbar-thumb:hover { background: #1187ff; }f-element.small { height: 18px;}f-element.large { height: 36px;}body.connecting f-node:not(.io-connect) f-element:hover,f-element.select { background-color: rgba(61, 70, 82, 0.98);}f-element.invalid > f-io { zoom: 1 !important;}f-element.invalid::after { font-size: 14px !important; display: flex; justify-content: center; align-items:center; margin: auto; position: absolute; width: 100%; height: 100%; background: #bd0b0b77; vertical-align: middle; color: #fff; content: 'Not Compatible'; opacity: .95; backdrop-filter: grayscale(100%); white-space: nowrap; overflow: hidden; left: 0; top: 0; text-transform: initial;}f-element.invalid > f-inputs,f-element.invalid > f-label { opacity: .1;}f-drop { width: 100%; height: 100%; position: sticky; left: 0; top: 0; background: #02358417; text-align: center; justify-content: center; align-items: center; display: flex; box-shadow: inset 0 0 20px 10px #464ace17; pointer-events: none; transition: all .07s; opacity: 0; visibility: hidden;}f-drop.visible { visibility: unset; opacity: unset; transition: all .23s;}f-drop span { opacity: .5; font-size: 40px; text-shadow: 0px 0px 5px #000; font-weight: bold;}f-tooltip { pointer-events: none;}f-tooltip { position: absolute; left: 0; top: 0; background: rgba(0,0,0,.8); backdrop-filter: blur(4px); font-size: 14px; padding: 7px; left: 50%; border-radius: 10px; transform: translateX(-50%); visibility: hidden; pointer-events: none; opacity: 0; transition: all 0.3s ease; z-index: 150; white-space: nowrap;}f-menu.context,f-menu.search { position: absolute;}f-menu.context { width: 170px; z-index: 110;}f-menu.search { bottom: 85px; left: 50%; transform: translateX(-50%); z-index: 10; width: 300px;}f-menu.context f-list { display: block; margin: 0; background: #171717e6; font-size: 12px; border-radius: 6px; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; box-shadow: 3px 3px 6px rgba(0,0,0,.2); transition: opacity 0.2s ease, transform 0.1s ease;}f-menu.search f-list { margin: 0 6px 0 6px; display: flex; flex-direction: column-reverse; margin-bottom: 5px;}f-menu.context.hidden { visibility: hidden; opacity: 0;}f-menu.context f-item,f-menu.search f-item { display: block; position: relative; margin: 0; padding: 0; white-space: nowrap;}f-menu.search f-item { opacity: 0;}f-menu.context f-item.submenu::after { content: ""; position: absolute; right: 6px; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); border: 5px solid transparent; border-left-color: #808080;}f-menu.context f-item:hover > f-menu,f-menu.context f-item.active > f-menu { visibility: unset; transform: unset; opacity: unset;}f-menu.context f-menu { top: 0px; left: calc( 100% - 4px );}f-menu.context f-item button,f-menu.search f-item button { overflow: visible; display: block; width: calc( 100% - 6px ); text-align: left; cursor: pointer; white-space: nowrap; padding: 6px 8px; border-radius: 3px; background: rgba(45, 45, 48, 0.95); border: 0; color: #ddd; margin: 3px; text-shadow: 1px 1px 0px #0007;}f-menu.context f-item button i,f-menu.search f-item button i { float: left; font-size: 16px;}f-menu.context f-item button span,f-menu.search f-item button span { margin-left: 6px;}f-menu.context f-item:hover > button,f-menu.search f-item:hover > button,f-menu.search f-item.active > button { color: #fff; background-color: rgba(61, 70, 82, 0.98);}f-menu.search f-item:hover,f-menu.search f-item.active { opacity: 1 !important;}f-menu.context f-item button:active { outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-menu.context f-item f-tooltip { margin-left: 85px; top: -50px;}f-menu.search f-item { display: none;}f-menu.search f-item:nth-child(1) { opacity: 1; display: unset;}f-menu.search f-item:nth-child(2) { opacity: .8; display: unset;}f-menu.search f-item:nth-child(3) { opacity: .6; display: unset;}f-menu.search f-item:nth-child(4) { opacity: .4; display: unset;}f-menu.search f-item button { border-radius: 14px;}f-tips { right: 10px; top: 10px; position: absolute; z-index: 100; pointer-events: none; display: flex; flex-direction: column;}f-tips f-tip { width: 450px; font-size: 13px; border-radius: 6px; text-align: center; display: block; height: auto; color: #ffffffe0; margin: 4px; padding: 4px; background: #17171794; border: 1px solid #7e7e7e38; line-height: 100%; backdrop-filter: blur(6px); transition: all 0.2s ease; text-transform: initial; opacity: 0;}f-tips f-tip:nth-child(1) { opacity: 1;}f-tips f-tip:nth-child(2) { opacity: .75;}f-tips f-tip:nth-child(3) { opacity: .25;}f-tips f-tip:nth-child(4) { opacity: .1;}f-tips f-tip.error { background: #b900005e;}f-menu.search input { width: calc( 100% - 28px ); height: 41px; position: absolute; z-index: 10; border-radius: 20px; padding-left: 14px; padding-right: 14px; font-size: 15px; background-color: #17171794; border: 1px solid #7e7e7e45; backdrop-filter: blur(6px); box-shadow: 3px 3px 6px rgb(0 0 0 / 20%); text-transform: initial;}f-menu.circle { position: absolute; left: 40px; bottom: 40px; z-index: 100;}f-menu.circle f-item { align-content: space-around; margin-right: 20px;}f-menu.circle f-item button { width: 47px; height: 47px; font-size: 22px; background: #17171794; border-radius: 50%; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; line-height: 100%; cursor: pointer; box-shadow: 3px 3px 6px rgba(0,0,0,.2);}f-menu.circle f-item f-tooltip { margin-top: -60px;}.f-rounded f-node f-element,.f-rounded f-node f-element.title.left { border-radius: 10px 5px 10px 5px;}.f-rounded f-node f-element input, .f-rounded f-node f-element select,.f-rounded f-node f-element button,.f-rounded f-node f-element textarea,.f-rounded f-node f-element input[type='checkbox'].toggle,.f-rounded f-node f-element input[type='checkbox'].toggle:after { border-radius: 20px 10px;}.f-rounded f-node f-element input { padding-left: 7px; padding-right: 7px;}.f-rounded f-menu.context,.f-rounded f-menu.context f-item button { border-radius: 20px 10px;}@media (hover: hover) and (pointer: fine) { f-node:not(.selected):hover { filter: drop-shadow(0 0 6px #66666630); } f-element f-toolbar { visibility: hidden; opacity: 0; transition: opacity 0.2s ease; } f-node:hover > f-element f-toolbar { visibility: visible; opacity: 1; } f-element f-io:hover { zoom: 1.4; } f-menu.circle f-item button:hover { background-color: #2a2a2a; } f-menu.search input:hover, f-menu.search input:focus { background-color: #1a1a1a; filter: drop-shadow(0 0 6px #66666630); } f-menu.search input:focus { filter: drop-shadow(0 0 8px #4444dd); } f-menu.circle f-item button:hover > f-tooltip, f-menu.context f-item button:hover > f-tooltip { visibility: visible; opacity: 1; } f-menu.circle f-item button:hover > f-tooltip { margin-top: -50px; } f-menu.context f-item button:hover > f-tooltip { top: -30px; } f-menu.circle f-item button:focus > f-tooltip, f-menu.context f-item button:focus > f-tooltip { visibility: hidden; opacity: 0; }}@media (hover: none) and (pointer: coarse) { body.dragging f-canvas, body.connecting f-canvas { overflow: hidden !important; }}f-canvas { will-change: top, left;}f-node { will-change: transform !important;}` );
20
20
 
21
21
  const REVISION = '1';
22
22
 
@@ -114,6 +114,50 @@ class Serializer extends EventTarget {
114
114
 
115
115
  }
116
116
 
117
+ class PointerMonitor {
118
+
119
+ started = false;
120
+
121
+ constructor() {
122
+
123
+ this.x = 0;
124
+ this.y = 0;
125
+
126
+ this._onMoveEvent = ( e ) => {
127
+
128
+ const event = e.touches ? e.touches[ 0 ] : e;
129
+
130
+ this.x = event.x;
131
+ this.y = event.y;
132
+
133
+ };
134
+
135
+ }
136
+
137
+ start() {
138
+
139
+ if ( this.started ) return;
140
+
141
+ this.started = true;
142
+
143
+ window.addEventListener( 'wheel', this._onMoveEvent, true );
144
+
145
+ window.addEventListener( 'mousedown', this._onMoveEvent, true );
146
+ window.addEventListener( 'touchstart', this._onMoveEvent, true );
147
+
148
+ window.addEventListener( 'mousemove', this._onMoveEvent, true );
149
+ window.addEventListener( 'touchmove', this._onMoveEvent, true );
150
+
151
+ window.addEventListener( 'dragover', this._onMoveEvent, true );
152
+
153
+ return this;
154
+
155
+ }
156
+
157
+ }
158
+
159
+ const pointer = new PointerMonitor().start();
160
+
117
161
  const draggableDOM = ( dom, callback = null, className = 'dragging' ) => {
118
162
 
119
163
  let dragData = null;
@@ -259,10 +303,16 @@ const dispatchEventList = ( list, ...params ) => {
259
303
 
260
304
  for ( const callback of list ) {
261
305
 
262
- callback( ...params );
306
+ if ( callback( ...params ) === false ) {
307
+
308
+ return false;
309
+
310
+ }
263
311
 
264
312
  }
265
313
 
314
+ return true;
315
+
266
316
  };
267
317
 
268
318
  const toPX = ( val ) => {
@@ -291,6 +341,7 @@ const toHex = ( val ) => {
291
341
 
292
342
  var Utils = /*#__PURE__*/Object.freeze({
293
343
  __proto__: null,
344
+ pointer: pointer,
294
345
  draggableDOM: draggableDOM,
295
346
  dispatchEventList: dispatchEventList,
296
347
  toPX: toPX,
@@ -370,7 +421,17 @@ class Element extends Serializer {
370
421
 
371
422
  }
372
423
 
373
- selected = element;
424
+ const type = e.type;
425
+
426
+ if ( ( type === 'mouseout' ) && selected === element ) {
427
+
428
+ selected = null;
429
+
430
+ } else {
431
+
432
+ selected = element;
433
+
434
+ }
374
435
 
375
436
  };
376
437
 
@@ -385,6 +446,9 @@ class Element extends Serializer {
385
446
  }
386
447
 
387
448
  dom.addEventListener( 'mouseup', onSelect, true );
449
+ dom.addEventListener( 'mouseover', onSelect );
450
+ dom.addEventListener( 'mouseout', onSelect );
451
+ dom.addEventListener( 'touchmove', onSelect );
388
452
  dom.addEventListener( 'touchend', onSelect );
389
453
 
390
454
  this.inputs = [];
@@ -398,14 +462,15 @@ class Element extends Serializer {
398
462
 
399
463
  this.events = {
400
464
  'connect': [],
401
- 'connectChildren': []
465
+ 'connectChildren': [],
466
+ 'valid': []
402
467
  };
403
468
 
404
469
  this.node = null;
405
470
 
406
471
  this.style = '';
407
472
 
408
- this.extra = null;
473
+ this.objectCallback = null;
409
474
 
410
475
  this.enabledInputs = true;
411
476
 
@@ -418,10 +483,10 @@ class Element extends Serializer {
418
483
  this.lioDOM = this._createIO( 'lio' );
419
484
  this.rioDOM = this._createIO( 'rio' );
420
485
 
421
- this.dom.classList.add( `output-${ Link.InputDirection }` );
486
+ this.dom.classList.add( `input-${ Link.InputDirection }` );
422
487
 
423
- this.dom.appendChild( this.lioDOM );
424
- this.dom.appendChild( this.rioDOM );
488
+ this.dom.append( this.lioDOM );
489
+ this.dom.append( this.rioDOM );
425
490
 
426
491
  this.addEventListener( 'connect', ( ) => {
427
492
 
@@ -437,6 +502,22 @@ class Element extends Serializer {
437
502
 
438
503
  }
439
504
 
505
+ setAttribute( name, value ) {
506
+
507
+ this.dom.setAttribute( name, value );
508
+
509
+ return this;
510
+
511
+ }
512
+
513
+ onValid( callback ) {
514
+
515
+ this.events.valid.push( callback );
516
+
517
+ return this;
518
+
519
+ }
520
+
440
521
  onConnect( callback, childrens = false ) {
441
522
 
442
523
  this.events.connect.push( callback );
@@ -451,17 +532,17 @@ class Element extends Serializer {
451
532
 
452
533
  }
453
534
 
454
- setExtra( value ) {
535
+ setObjectCallback( callback ) {
455
536
 
456
- this.extra = value;
537
+ this.objectCallback = callback;
457
538
 
458
539
  return this;
459
540
 
460
541
  }
461
542
 
462
- getExtra() {
543
+ getObject( output = null ) {
463
544
 
464
- return this.extra;
545
+ return this.objectCallback ? this.objectCallback( output ) : null;
465
546
 
466
547
  }
467
548
 
@@ -485,9 +566,9 @@ class Element extends Serializer {
485
566
 
486
567
  const dom = this.dom;
487
568
 
488
- if ( !this.enabledInputs ) dom.classList.remove( 'inputs-disable');
569
+ if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' );
489
570
 
490
- if ( !value ) dom.classList.add( 'inputs-disable' );
571
+ if ( ! value ) dom.classList.add( 'inputs-disable' );
491
572
 
492
573
  this.enabledInputs = value;
493
574
 
@@ -501,6 +582,14 @@ class Element extends Serializer {
501
582
 
502
583
  }
503
584
 
585
+ setColor( color ) {
586
+
587
+ this.dom.style[ 'background-color' ] = toHex( color );
588
+
589
+ return this;
590
+
591
+ }
592
+
504
593
  setStyle( style ) {
505
594
 
506
595
  const dom = this.dom;
@@ -529,6 +618,20 @@ class Element extends Serializer {
529
618
 
530
619
  }
531
620
 
621
+ setInputColor( color ) {
622
+
623
+ if ( Link.InputDirection === 'left' ) {
624
+
625
+ return this.setLIOColor( color );
626
+
627
+ } else {
628
+
629
+ return this.setRIOColor( color );
630
+
631
+ }
632
+
633
+ }
634
+
532
635
  setOutput( length ) {
533
636
 
534
637
  if ( Link.InputDirection === 'left' ) {
@@ -543,6 +646,20 @@ class Element extends Serializer {
543
646
 
544
647
  }
545
648
 
649
+ setOutputColor( color ) {
650
+
651
+ if ( Link.InputDirection === 'left' ) {
652
+
653
+ return this.setRIOColor( color );
654
+
655
+ } else {
656
+
657
+ return this.setLIOColor( color );
658
+
659
+ }
660
+
661
+ }
662
+
546
663
  get inputLength() {
547
664
 
548
665
  if ( Link.InputDirection === 'left' ) {
@@ -571,6 +688,14 @@ class Element extends Serializer {
571
688
 
572
689
  }
573
690
 
691
+ setLIOColor( color ) {
692
+
693
+ this.lioDOM.style[ 'border-color' ] = toHex( color );
694
+
695
+ return this;
696
+
697
+ }
698
+
574
699
  setLIO( length ) {
575
700
 
576
701
  this.lioLength = length;
@@ -581,6 +706,26 @@ class Element extends Serializer {
581
706
 
582
707
  }
583
708
 
709
+ getLIOColor() {
710
+
711
+ return this.lioDOM.style[ 'border-color' ];
712
+
713
+ }
714
+
715
+ setRIOColor( color ) {
716
+
717
+ this.rioDOM.style[ 'border-color' ] = toHex( color );
718
+
719
+ return this;
720
+
721
+ }
722
+
723
+ getRIOColor() {
724
+
725
+ return this.rioDOM.style[ 'border-color' ];
726
+
727
+ }
728
+
584
729
  setRIO( length ) {
585
730
 
586
731
  this.rioLength = length;
@@ -597,7 +742,7 @@ class Element extends Serializer {
597
742
 
598
743
  input.element = this;
599
744
 
600
- this.inputsDOM.appendChild( input.dom );
745
+ this.inputsDOM.append( input.dom );
601
746
 
602
747
  return this;
603
748
 
@@ -629,6 +774,12 @@ class Element extends Serializer {
629
774
 
630
775
  if ( element !== null ) {
631
776
 
777
+ if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) {
778
+
779
+ return false;
780
+
781
+ }
782
+
632
783
  const link = new Link( this, element );
633
784
 
634
785
  this.links.push( link );
@@ -638,7 +789,7 @@ class Element extends Serializer {
638
789
  this.disconnectDOM = document.createElement( 'f-disconnect' );
639
790
  this.disconnectDOM.innerHTML = Styles.icons.unlink ? `<i class='${ Styles.icons.unlink }'></i>` : '✖';
640
791
 
641
- this.dom.appendChild( this.disconnectDOM );
792
+ this.dom.append( this.disconnectDOM );
642
793
 
643
794
  const onDisconnect = () => {
644
795
 
@@ -682,6 +833,7 @@ class Element extends Serializer {
682
833
  this.disconnectDOM.addEventListener( 'mousedown', onClick, true );
683
834
  this.disconnectDOM.addEventListener( 'touchstart', onClick, true );
684
835
  this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true );
836
+
685
837
  element.addEventListener( 'connect', onConnect );
686
838
  element.addEventListener( 'connectChildren', onConnect );
687
839
  element.addEventListener( 'nodeConnect', onConnect );
@@ -694,7 +846,7 @@ class Element extends Serializer {
694
846
 
695
847
  this.dispatchEvent( new Event( 'connect' ) );
696
848
 
697
- return this;
849
+ return true;
698
850
 
699
851
  }
700
852
 
@@ -802,23 +954,23 @@ class Element extends Serializer {
802
954
 
803
955
  }
804
956
 
805
- get linkedExtra() {
957
+ getLinkedObject( output = null ) {
806
958
 
807
- const linkedElement = this.linkedElement;
959
+ const linkedElement = this.getLinkedElement();
808
960
 
809
- return linkedElement ? linkedElement.getExtra() : null;
961
+ return linkedElement ? linkedElement.getObject( output ) : null;
810
962
 
811
963
  }
812
964
 
813
- get linkedElement() {
965
+ getLinkedElement() {
814
966
 
815
- const link = this.link;
967
+ const link = this.getLink();
816
968
 
817
969
  return link ? link.outputElement : null;
818
970
 
819
971
  }
820
972
 
821
- get link() {
973
+ getLink() {
822
974
 
823
975
  return this.links[ 0 ];
824
976
 
@@ -850,31 +1002,66 @@ class Element extends Serializer {
850
1002
  const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio';
851
1003
 
852
1004
  const link = type === defaultOutput ? new Link( this ) : new Link( null, this );
1005
+ const previewLink = new Link( link.inputElement, link.outputElement );
853
1006
 
854
1007
  this.links.push( link );
855
1008
 
856
1009
  draggableDOM( e, ( data ) => {
857
1010
 
858
- if ( data.dragging === false ) {
1011
+ if ( previewLink.outputElement )
1012
+ previewLink.outputElement.dom.classList.remove( 'invalid' );
859
1013
 
860
- nodeDOM.classList.remove( 'io-connect' );
1014
+ if ( previewLink.inputElement )
1015
+ previewLink.inputElement.dom.classList.remove( 'invalid' );
861
1016
 
862
- ioDOM.classList.remove( 'connect' );
863
- dom.classList.remove( 'select' );
1017
+ previewLink.inputElement = link.inputElement;
1018
+ previewLink.outputElement = link.outputElement;
864
1019
 
865
- this.links.splice( this.links.indexOf( link ), 1 );
1020
+ if ( type === defaultOutput ) {
866
1021
 
867
- if ( selected !== null ) {
1022
+ previewLink.outputElement = selected;
868
1023
 
869
- if ( type === defaultOutput ) {
1024
+ } else {
870
1025
 
871
- link.outputElement = selected;
1026
+ previewLink.inputElement = selected;
872
1027
 
873
- } else {
1028
+ }
874
1029
 
875
- link.inputElement = selected;
1030
+ const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null &&
1031
+ previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 &&
1032
+ dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false;
876
1033
 
877
- }
1034
+ if ( data.dragging && isInvalid ) {
1035
+
1036
+ if ( type === defaultOutput ) {
1037
+
1038
+ if ( previewLink.outputElement )
1039
+ previewLink.outputElement.dom.classList.add( 'invalid' );
1040
+
1041
+ } else {
1042
+
1043
+ if ( previewLink.inputElement )
1044
+ previewLink.inputElement.dom.classList.add( 'invalid' );
1045
+
1046
+ }
1047
+
1048
+ return;
1049
+
1050
+ }
1051
+
1052
+ if ( ! data.dragging ) {
1053
+
1054
+ nodeDOM.classList.remove( 'io-connect' );
1055
+
1056
+ ioDOM.classList.remove( 'connect' );
1057
+ dom.classList.remove( 'select' );
1058
+
1059
+ this.links.splice( this.links.indexOf( link ), 1 );
1060
+
1061
+ if ( selected !== null && ! isInvalid ) {
1062
+
1063
+ link.inputElement = previewLink.inputElement;
1064
+ link.outputElement = previewLink.outputElement;
878
1065
 
879
1066
  // check if is an is circular link
880
1067
 
@@ -923,6 +1110,8 @@ class Input extends Serializer {
923
1110
 
924
1111
  this.extra = null;
925
1112
 
1113
+ this.tagColor = null;
1114
+
926
1115
  this.events = {
927
1116
  'change': [],
928
1117
  'click': []
@@ -956,12 +1145,28 @@ class Input extends Serializer {
956
1145
 
957
1146
  }
958
1147
 
1148
+ setTagColor( color ) {
1149
+
1150
+ this.tagColor = color;
1151
+
1152
+ this.dom.style[ 'border-left' ] = `2px solid ${color}`;
1153
+
1154
+ return this;
1155
+
1156
+ }
1157
+
1158
+ getTagColor() {
1159
+
1160
+ return this.tagColor;
1161
+
1162
+ }
1163
+
959
1164
  setToolTip( text ) {
960
1165
 
961
1166
  const div = document.createElement( 'f-tooltip' );
962
1167
  div.innerText = text;
963
1168
 
964
- this.dom.appendChild( div );
1169
+ this.dom.append( div );
965
1170
 
966
1171
  return this;
967
1172
 
@@ -983,6 +1188,20 @@ class Input extends Serializer {
983
1188
 
984
1189
  }
985
1190
 
1191
+ setReadOnly( value ) {
1192
+
1193
+ this.dom.readOnly = value;
1194
+
1195
+ return this;
1196
+
1197
+ }
1198
+
1199
+ getReadOnly() {
1200
+
1201
+ return this.dom.readOnly;
1202
+
1203
+ }
1204
+
986
1205
  setValue( value, dispatch = true ) {
987
1206
 
988
1207
  this.dom.value = value;
@@ -1161,7 +1380,7 @@ class Node extends Serializer {
1161
1380
  element.addEventListener( 'connect', this._onConnect );
1162
1381
  element.addEventListener( 'connectChildren', this._onConnectChildren );
1163
1382
 
1164
- this.dom.appendChild( element.dom );
1383
+ this.dom.append( element.dom );
1165
1384
 
1166
1385
  return this;
1167
1386
 
@@ -1347,9 +1566,9 @@ class TitleElement extends DraggableElement {
1347
1566
  this.iconDOM = iconDOM;
1348
1567
  this.toolbarDOM = toolbarDOM;
1349
1568
 
1350
- dom.appendChild( spanDOM );
1351
- dom.appendChild( iconDOM );
1352
- dom.appendChild( toolbarDOM );
1569
+ dom.append( spanDOM );
1570
+ dom.append( iconDOM );
1571
+ dom.append( toolbarDOM );
1353
1572
 
1354
1573
  }
1355
1574
 
@@ -1385,7 +1604,7 @@ class TitleElement extends DraggableElement {
1385
1604
 
1386
1605
  this.buttons.push( button );
1387
1606
 
1388
- this.toolbarDOM.appendChild( button.dom );
1607
+ this.toolbarDOM.append( button.dom );
1389
1608
 
1390
1609
  return this;
1391
1610
 
@@ -1424,9 +1643,11 @@ class TitleElement extends DraggableElement {
1424
1643
 
1425
1644
  }
1426
1645
 
1427
- const drawLine = ( p1x, p1y, p2x, p2y, invert, size, color, ctx ) => {
1646
+ const drawLine = ( p1x, p1y, p2x, p2y, invert, size, colorA, ctx, colorB = null ) => {
1428
1647
 
1429
- const offset = 100 * ( invert ? - 1 : 1 );
1648
+ const dx = p2x - p1x;
1649
+ const dy = p2y - p1y;
1650
+ const offset = Math.sqrt( dx*dx + dy*dy ) * (invert ? -.3 : .3) ;
1430
1651
 
1431
1652
  ctx.beginPath();
1432
1653
 
@@ -1438,8 +1659,21 @@ const drawLine = ( p1x, p1y, p2x, p2y, invert, size, color, ctx ) => {
1438
1659
  p2x, p2y
1439
1660
  );
1440
1661
 
1662
+ if ( colorB !== null && colorA !== colorB ) {
1663
+
1664
+ const gradient = ctx.createLinearGradient( p1x, p1y, p2x, p2y );
1665
+ gradient.addColorStop( 0, colorA );
1666
+ gradient.addColorStop( 1, colorB );
1667
+
1668
+ ctx.strokeStyle = gradient;
1669
+
1670
+ } else {
1671
+
1672
+ ctx.strokeStyle = colorA;
1673
+
1674
+ }
1675
+
1441
1676
  ctx.lineWidth = size;
1442
- ctx.strokeStyle = color;
1443
1677
  ctx.stroke();
1444
1678
 
1445
1679
  };
@@ -1514,11 +1748,11 @@ class Canvas extends Serializer {
1514
1748
 
1515
1749
  dropDOM.innerHTML = '<span>drop your file</span>';
1516
1750
 
1517
- dom.appendChild( dropDOM );
1518
- dom.appendChild( canvas );
1519
- dom.appendChild( frontCanvas );
1520
- dom.appendChild( contentDOM );
1521
- dom.appendChild( areaDOM );
1751
+ dom.append( dropDOM );
1752
+ dom.append( canvas );
1753
+ dom.append( frontCanvas );
1754
+ dom.append( contentDOM );
1755
+ dom.append( areaDOM );
1522
1756
  /*
1523
1757
  let zoomTouchData = null;
1524
1758
 
@@ -1790,11 +2024,13 @@ class Canvas extends Serializer {
1790
2024
 
1791
2025
  add( node ) {
1792
2026
 
2027
+ if ( node.canvas === this ) return;
2028
+
1793
2029
  this.nodes.push( node );
1794
2030
 
1795
2031
  node.canvas = this;
1796
2032
 
1797
- this.contentDOM.appendChild( node.dom );
2033
+ this.contentDOM.append( node.dom );
1798
2034
 
1799
2035
  return this;
1800
2036
 
@@ -1818,6 +2054,8 @@ class Canvas extends Serializer {
1818
2054
 
1819
2055
  this.contentDOM.removeChild( node.dom );
1820
2056
 
2057
+ node.dispatchEvent( new Event( 'remove' ) );
2058
+
1821
2059
  return this;
1822
2060
 
1823
2061
  }
@@ -1842,10 +2080,18 @@ class Canvas extends Serializer {
1842
2080
 
1843
2081
  for ( const link of links ) {
1844
2082
 
1845
- if ( link.outputElement && link.outputElement.node === node ) {
2083
+ if ( link.inputElement && link.outputElement ) {
2084
+
2085
+ if ( link.inputElement.node === node ) {
2086
+
2087
+ link.inputElement.connect();
2088
+
2089
+ } else if ( link.outputElement.node === node ) {
1846
2090
 
1847
- link.inputElement.connect();
2091
+ link.inputElement.connect();
1848
2092
 
2093
+ }
2094
+
1849
2095
  }
1850
2096
 
1851
2097
  }
@@ -1989,24 +2235,36 @@ class Canvas extends Serializer {
1989
2235
 
1990
2236
  if ( draggingLink || length === 1 ) {
1991
2237
 
2238
+ let colorA = null,
2239
+ colorB = null;
2240
+
1992
2241
  if ( draggingLink === 'rio' ) {
1993
2242
 
2243
+ colorA = colorB = lioElement.getRIOColor();
2244
+
1994
2245
  aPos.x += offsetIORadius;
1995
2246
  bPos.x /= zoom;
1996
2247
  bPos.y /= zoom;
1997
2248
 
1998
2249
  } else if ( draggingLink === 'lio' ) {
1999
2250
 
2251
+ colorA = colorB = rioElement.getLIOColor();
2252
+
2000
2253
  bPos.x -= offsetIORadius;
2001
2254
  aPos.x /= zoom;
2002
2255
  aPos.y /= zoom;
2003
2256
 
2257
+ } else {
2258
+
2259
+ colorA = lioElement.getRIOColor();
2260
+ colorB = rioElement.getLIOColor();
2261
+
2004
2262
  }
2005
2263
 
2006
2264
  drawLine(
2007
2265
  aPos.x * zoom, aPos.y * zoom,
2008
2266
  bPos.x * zoom, bPos.y * zoom,
2009
- false, 2, '#ffffff', drawContext
2267
+ false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff'
2010
2268
  );
2011
2269
 
2012
2270
  } else {
@@ -2022,6 +2280,9 @@ class Canvas extends Serializer {
2022
2280
  const rioLength = Math.min( lioElement.rioLength, length );
2023
2281
  const lioLength = Math.min( rioElement.lioLength, length );
2024
2282
 
2283
+ const colorA = lioElement.getRIOColor() || color;
2284
+ const colorB = rioElement.getLIOColor() || color;
2285
+
2025
2286
  const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 );
2026
2287
  const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 );
2027
2288
 
@@ -2034,7 +2295,7 @@ class Canvas extends Serializer {
2034
2295
  drawLine(
2035
2296
  aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom,
2036
2297
  bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom,
2037
- false, 2, color, drawContext
2298
+ false, 2, colorA, drawContext, colorB
2038
2299
  );
2039
2300
 
2040
2301
  }
@@ -2093,10 +2354,10 @@ class ButtonInput extends Input {
2093
2354
  const dom = document.createElement( 'button' );
2094
2355
 
2095
2356
  const spanDOM = document.createElement( 'span' );
2096
- dom.appendChild( spanDOM );
2357
+ dom.append( spanDOM );
2097
2358
 
2098
2359
  const iconDOM = document.createElement( 'i' );
2099
- dom.appendChild( iconDOM );
2360
+ dom.append( iconDOM );
2100
2361
 
2101
2362
  super( dom );
2102
2363
 
@@ -2149,14 +2410,15 @@ class ButtonInput extends Input {
2149
2410
 
2150
2411
  class ObjectNode extends Node {
2151
2412
 
2152
- constructor( name, inputLength, extra = null, width = 300 ) {
2413
+ constructor( name, inputLength, callback = null, width = 300 ) {
2153
2414
 
2154
2415
  super();
2155
2416
 
2156
2417
  this.setWidth( width );
2157
2418
 
2158
2419
  const title = new TitleElement( name )
2159
- .setExtra( extra )
2420
+ .setObjectCallback( callback )
2421
+ .setSerializable( false )
2160
2422
  .setOutput( inputLength );
2161
2423
 
2162
2424
  const closeButton = new ButtonInput( Styles.icons.close || '✕' ).onClick( () => {
@@ -2174,17 +2436,43 @@ class ObjectNode extends Node {
2174
2436
 
2175
2437
  }
2176
2438
 
2177
- setExtra( value ) {
2439
+ setName( value ) {
2440
+
2441
+ this.title.setTitle( value );
2442
+
2443
+ return this;
2444
+
2445
+ }
2446
+
2447
+ getName() {
2448
+
2449
+ return this.title.getTitle();
2450
+
2451
+ }
2452
+
2453
+ setObjectCallback( callback ) {
2178
2454
 
2179
- this.title.setExtra( value );
2455
+ this.title.setObjectCallback( callback );
2180
2456
 
2181
2457
  return this;
2182
2458
 
2183
2459
  }
2184
2460
 
2185
- getExtra( value ) {
2461
+ getObject( callback ) {
2462
+
2463
+ return this.title.getObject( callback );
2464
+
2465
+ }
2466
+
2467
+ setColor( color ) {
2468
+
2469
+ return this.title.setColor( color );
2470
+
2471
+ }
2472
+
2473
+ setOutputColor( color ) {
2186
2474
 
2187
- return this.title.getExtra();
2475
+ return this.title.setOutputColor( color );
2188
2476
 
2189
2477
  }
2190
2478
 
@@ -2327,6 +2615,14 @@ class NumberInput extends Input {
2327
2615
 
2328
2616
  }
2329
2617
 
2618
+ setStep( step ) {
2619
+
2620
+ this.step = step;
2621
+
2622
+ return this;
2623
+
2624
+ }
2625
+
2330
2626
  setRange( min, max, step ) {
2331
2627
 
2332
2628
  this.min = min;
@@ -2452,8 +2748,8 @@ class SliderInput extends Input {
2452
2748
 
2453
2749
  } );
2454
2750
 
2455
- dom.appendChild( rangeDOM );
2456
- dom.appendChild( field.dom );
2751
+ dom.append( rangeDOM );
2752
+ dom.append( field.dom );
2457
2753
 
2458
2754
  this.rangeDOM = rangeDOM;
2459
2755
  this.field = field;
@@ -2625,11 +2921,13 @@ class LabelElement extends Element {
2625
2921
  this.spanDOM = spanDOM;
2626
2922
  this.iconDOM = iconDOM;
2627
2923
 
2628
- this.labelDOM.appendChild( this.spanDOM );
2629
- this.labelDOM.appendChild( this.iconDOM );
2924
+ this.labelDOM.append( this.spanDOM );
2925
+ this.labelDOM.append( this.iconDOM );
2926
+
2927
+ this.dom.append( this.labelDOM );
2928
+ this.dom.append( this.inputsDOM );
2630
2929
 
2631
- this.dom.appendChild( this.labelDOM );
2632
- this.dom.appendChild( this.inputsDOM );
2930
+ this.serializeLabel = false;
2633
2931
 
2634
2932
  this.setLabel( label );
2635
2933
  this.setAlign( align );
@@ -2672,15 +2970,19 @@ class LabelElement extends Element {
2672
2970
 
2673
2971
  super.serialize( data );
2674
2972
 
2675
- const label = this.getLabel();
2676
- const icon = this.getIcon();
2973
+ if ( this.serializeLabel ) {
2677
2974
 
2678
- data.label = label;
2975
+ const label = this.getLabel();
2976
+ const icon = this.getIcon();
2679
2977
 
2680
- if ( icon !== '' ) {
2978
+ data.label = label;
2681
2979
 
2682
- data.icon = icon;
2980
+ if ( icon !== '' ) {
2981
+
2982
+ data.icon = icon;
2683
2983
 
2984
+ }
2985
+
2684
2986
  }
2685
2987
 
2686
2988
  }
@@ -2689,12 +2991,16 @@ class LabelElement extends Element {
2689
2991
 
2690
2992
  super.deserialize( data );
2691
2993
 
2692
- this.setLabel( data.label );
2994
+ if ( this.serializeLabel ) {
2693
2995
 
2694
- if ( data.icon !== undefined ) {
2996
+ this.setLabel( data.label );
2695
2997
 
2696
- this.setIcon( data.icon );
2998
+ if ( data.icon !== undefined ) {
2999
+
3000
+ this.setIcon( data.icon );
2697
3001
 
3002
+ }
3003
+
2698
3004
  }
2699
3005
 
2700
3006
  }
@@ -2824,61 +3130,161 @@ class PanelNode extends Node {
2824
3130
 
2825
3131
  class Menu extends EventTarget {
2826
3132
 
2827
- constructor( className, target = null ) {
3133
+ constructor( className ) {
2828
3134
 
2829
3135
  super();
2830
3136
 
2831
3137
  const dom = document.createElement( 'f-menu' );
2832
3138
  dom.className = className + ' hidden';
2833
3139
 
3140
+ const listDOM = document.createElement( 'f-list' );
3141
+
3142
+ dom.append( listDOM );
3143
+
2834
3144
  this.dom = dom;
3145
+ this.listDOM = listDOM;
2835
3146
 
2836
3147
  this.visible = false;
2837
3148
 
2838
3149
  this.subMenus = new WeakMap();
2839
3150
  this.domButtons = new WeakMap();
2840
3151
 
2841
- this.events = {
2842
- 'context': []
2843
- };
3152
+ this.buttons = [];
2844
3153
 
2845
- this.addEventListener( 'context', ( ) => {
3154
+ this.events = {};
2846
3155
 
2847
- dispatchEventList( this.events.context, this );
3156
+ }
2848
3157
 
2849
- } );
3158
+ onContext( callback ) {
2850
3159
 
2851
- this._lastButtonClick = null;
3160
+ this.events.context.push( callback );
2852
3161
 
2853
- this._onButtonClick = ( e = null ) => {
3162
+ return this;
2854
3163
 
2855
- const button = e ? e.target : null;
3164
+ }
2856
3165
 
2857
- if ( this._lastButtonClick ) {
3166
+ show() {
2858
3167
 
2859
- this._lastButtonClick.dom.parentElement.classList.remove( 'active' );
3168
+ this.dom.classList.remove( 'hidden' );
2860
3169
 
2861
- }
3170
+ this.visible = true;
2862
3171
 
2863
- this._lastButtonClick = button;
3172
+ this.dispatchEvent( new Event( 'show' ) );
2864
3173
 
2865
- if ( button ) {
3174
+ return this;
2866
3175
 
2867
- if ( this.subMenus.has( button ) ) {
3176
+ }
2868
3177
 
2869
- this.subMenus.get( button )._onButtonClick();
3178
+ hide() {
2870
3179
 
2871
- }
3180
+ this.dom.classList.add( 'hidden' );
2872
3181
 
2873
- button.dom.parentElement.classList.add( 'active' );
3182
+ this.dispatchEvent( new Event( 'hide' ) );
2874
3183
 
2875
- }
3184
+ this.visible = false;
2876
3185
 
2877
- };
3186
+ }
2878
3187
 
2879
- this._onButtonMouseOver = ( e ) => {
3188
+ add( button, submenu = null ) {
2880
3189
 
2881
- const button = e.target;
3190
+ const liDOM = document.createElement( 'f-item' );
3191
+
3192
+ if ( submenu !== null ) {
3193
+
3194
+ liDOM.classList.add( 'submenu' );
3195
+
3196
+ liDOM.append( submenu.dom );
3197
+
3198
+ this.subMenus.set( button, submenu );
3199
+
3200
+ button.dom.addEventListener( 'mouseover', () => submenu.show() );
3201
+ button.dom.addEventListener( 'mouseout', () => submenu.hide() );
3202
+
3203
+ }
3204
+
3205
+ liDOM.append( button.dom );
3206
+
3207
+ this.buttons.push( button );
3208
+
3209
+ this.listDOM.append( liDOM );
3210
+
3211
+ this.domButtons.set( button, liDOM );
3212
+
3213
+ return this;
3214
+
3215
+ }
3216
+
3217
+ clear() {
3218
+
3219
+ this.buttons = [];
3220
+
3221
+ this.subMenus = new WeakMap();
3222
+ this.domButtons = new WeakMap();
3223
+
3224
+ while ( this.listDOM.firstChild ) {
3225
+
3226
+ this.listDOM.firstChild.remove();
3227
+
3228
+ }
3229
+
3230
+ }
3231
+
3232
+ }
3233
+
3234
+ let lastContext = null;
3235
+
3236
+ const onCloseLastContext = ( e ) => {
3237
+
3238
+ if ( lastContext && lastContext.visible === true && e.target.closest( 'f-menu.context' ) === null ) {
3239
+
3240
+ lastContext.hide();
3241
+
3242
+ }
3243
+
3244
+ };
3245
+
3246
+ document.body.addEventListener( 'mousedown', onCloseLastContext, true );
3247
+ document.body.addEventListener( 'touchstart', onCloseLastContext, true );
3248
+
3249
+ class ContextMenu extends Menu {
3250
+
3251
+ constructor( target = null ) {
3252
+
3253
+ super( 'context', target );
3254
+
3255
+ this.events.context = [];
3256
+
3257
+ this._lastButtonClick = null;
3258
+
3259
+ this._onButtonClick = ( e = null ) => {
3260
+
3261
+ const button = e ? e.target : null;
3262
+
3263
+ if ( this._lastButtonClick ) {
3264
+
3265
+ this._lastButtonClick.dom.parentElement.classList.remove( 'active' );
3266
+
3267
+ }
3268
+
3269
+ this._lastButtonClick = button;
3270
+
3271
+ if ( button ) {
3272
+
3273
+ if ( this.subMenus.has( button ) ) {
3274
+
3275
+ this.subMenus.get( button )._onButtonClick();
3276
+
3277
+ }
3278
+
3279
+ button.dom.parentElement.classList.add( 'active' );
3280
+
3281
+ }
3282
+
3283
+ };
3284
+
3285
+ this._onButtonMouseOver = ( e ) => {
3286
+
3287
+ const button = e.target;
2882
3288
 
2883
3289
  if ( this.subMenus.has( button ) && this._lastButtonClick !== button ) {
2884
3290
 
@@ -2888,45 +3294,50 @@ class Menu extends EventTarget {
2888
3294
 
2889
3295
  };
2890
3296
 
3297
+ this.addEventListener( 'context', ( ) => {
3298
+
3299
+ dispatchEventList( this.events.context, this );
3300
+
3301
+ } );
3302
+
2891
3303
  this.setTarget( target );
2892
3304
 
2893
3305
  }
2894
3306
 
2895
- onContext( callback ) {
3307
+ openFrom( dom ) {
2896
3308
 
2897
- this.events.context.push( callback );
3309
+ const rect = dom.getBoundingClientRect();
2898
3310
 
2899
- return this;
3311
+ return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) );
2900
3312
 
2901
3313
  }
2902
3314
 
2903
- show( x = null, y = null ) {
2904
-
2905
- this._onButtonClick();
3315
+ open( x = pointer.x, y = pointer.y ) {
2906
3316
 
2907
- if ( x !== null && y !== null ) {
3317
+ if ( lastContext !== null ) {
2908
3318
 
2909
- this.setPosition( x, y );
3319
+ lastContext.hide();
2910
3320
 
2911
3321
  }
2912
3322
 
2913
- this.dom.classList.remove( 'hidden' );
3323
+ lastContext = this;
2914
3324
 
2915
- this.visible = true;
3325
+ this.setPosition( x, y );
2916
3326
 
2917
- this.dispatchEvent( new Event( 'show' ) );
3327
+ document.body.append( this.dom );
2918
3328
 
2919
- return this;
3329
+ return this.show();
2920
3330
 
2921
3331
  }
2922
3332
 
2923
- hide() {
3333
+ setPosition( x, y ) {
2924
3334
 
2925
- this.dom.classList.add( 'hidden' );
3335
+ const dom = this.dom;
2926
3336
 
2927
- this.dispatchEvent( new Event( 'hide' ) );
3337
+ dom.style.left = toPX( x );
3338
+ dom.style.top = toPX( y );
2928
3339
 
2929
- this.visible = false;
3340
+ return this;
2930
3341
 
2931
3342
  }
2932
3343
 
@@ -2940,72 +3351,143 @@ class Menu extends EventTarget {
2940
3351
 
2941
3352
  if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return;
2942
3353
 
2943
- const rect = this.target.getBoundingClientRect();
2944
-
2945
3354
  this.dispatchEvent( new Event( 'context' ) );
2946
3355
 
2947
- this.show( e.pageX - rect.left, e.pageY - rect.top );
3356
+ this.open();
2948
3357
 
2949
3358
  };
2950
3359
 
2951
- const onDown = ( e ) => {
3360
+ this.target = target;
2952
3361
 
2953
- if ( this.visible === true && e.target.closest( 'f-menu' ) === null ) {
3362
+ target.addEventListener( 'contextmenu', onContextMenu, false );
2954
3363
 
2955
- this.hide();
3364
+ }
2956
3365
 
2957
- }
3366
+ return this;
2958
3367
 
2959
- };
3368
+ }
2960
3369
 
2961
- this.target = target;
3370
+ show() {
2962
3371
 
2963
- target.addEventListener( 'mousedown', onDown, true );
2964
- target.addEventListener( 'touchstart', onDown, true );
3372
+ if ( ! this.opened ) {
2965
3373
 
2966
- target.addEventListener( 'contextmenu', onContextMenu, false );
2967
-
2968
- target.appendChild( this.dom );
3374
+ this.dom.style.left = '';
3375
+ this.dom.style.transform = '';
2969
3376
 
2970
3377
  }
2971
3378
 
2972
- return this;
3379
+ const domRect = this.dom.getBoundingClientRect();
2973
3380
 
2974
- }
3381
+ let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 );
3382
+ let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 );
2975
3383
 
2976
- add( button, submenu = null ) {
3384
+ if ( this.opened ) {
2977
3385
 
2978
- const liDOM = document.createElement( 'f-item' );
3386
+ if ( offsetX < 0 ) offsetX = - domRect.width;
3387
+ if ( offsetY < 0 ) offsetY = - domRect.height;
2979
3388
 
2980
- if ( submenu !== null ) {
3389
+ this.setPosition( domRect.x + offsetX, domRect.y + offsetY );
2981
3390
 
2982
- liDOM.classList.add( 'submenu' );
3391
+ } else {
2983
3392
 
2984
- liDOM.appendChild( submenu.dom );
3393
+ // flip submenus
2985
3394
 
2986
- this.subMenus.set( button, submenu );
3395
+ if ( offsetX < 0 ) this.dom.style.left = '-100%';
3396
+ if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )';
2987
3397
 
2988
3398
  }
2989
3399
 
2990
- liDOM.appendChild( button.dom );
3400
+ return super.show();
3401
+
3402
+ }
3403
+
3404
+ hide() {
3405
+
3406
+ if ( this.opened ) {
3407
+
3408
+ lastContext = null;
3409
+
3410
+ }
3411
+
3412
+ return super.hide();
3413
+
3414
+ }
3415
+
3416
+ add( button, submenu = null ) {
2991
3417
 
2992
3418
  button.addEventListener( 'click', this._onButtonClick );
2993
3419
  button.addEventListener( 'mouseover', this._onButtonMouseOver );
2994
3420
 
2995
- this.dom.appendChild( liDOM );
3421
+ return super.add( button, submenu );
2996
3422
 
2997
- this.domButtons.set( liDOM, button );
3423
+ }
2998
3424
 
2999
- return this;
3425
+ get opened() {
3426
+
3427
+ return lastContext === this;
3000
3428
 
3001
3429
  }
3002
3430
 
3003
- setPosition( x, y ) {
3431
+ }
3004
3432
 
3005
- const dom = this.dom;
3433
+ class CircleMenu extends Menu {
3006
3434
 
3007
- dom.style.left = toPX( x );
3008
- dom.style.top = toPX( y );
3435
+ constructor( target = null ) {
3436
+
3437
+ super( 'circle', target );
3438
+
3439
+ }
3440
+
3441
+ }
3442
+
3443
+ class Tips extends EventTarget {
3444
+
3445
+ constructor() {
3446
+
3447
+ super();
3448
+
3449
+ const dom = document.createElement( 'f-tips' );
3450
+
3451
+ this.dom = dom;
3452
+
3453
+ this.time = 0;
3454
+ this.duration = 3000;
3455
+
3456
+ }
3457
+
3458
+ message( str ) {
3459
+
3460
+ return this.tip( str );
3461
+
3462
+ }
3463
+
3464
+ error( str ) {
3465
+
3466
+ return this.tip( str, 'error' );
3467
+
3468
+ }
3469
+
3470
+ tip( html, className = '' ) {
3471
+
3472
+ const dom = document.createElement( 'f-tip' );
3473
+ dom.className = className;
3474
+ dom.innerHTML = html;
3475
+
3476
+ this.dom.prepend( dom );
3477
+
3478
+ //requestAnimationFrame( () => dom.style.opacity = 1 );
3479
+
3480
+ this.time = Math.min( this.time + this.duration, this.duration );
3481
+
3482
+ setTimeout( () => {
3483
+
3484
+ this.time -= this.duration;
3485
+
3486
+ dom.style.opacity = 0;
3487
+
3488
+ setTimeout( () => dom.remove(), 250 );
3489
+
3490
+ }, this.time );
3009
3491
 
3010
3492
  return this;
3011
3493
 
@@ -3013,57 +3495,312 @@ class Menu extends EventTarget {
3013
3495
 
3014
3496
  }
3015
3497
 
3016
- let lastContext = null;
3498
+ const filterString = ( str ) => {
3017
3499
 
3018
- class ContextMenu extends Menu {
3500
+ return str.trim().toLowerCase().replace( /\s\s+/g, ' ' );
3019
3501
 
3020
- constructor( target = null ) {
3502
+ };
3021
3503
 
3022
- super( 'context', target );
3504
+ class Search extends Menu {
3505
+
3506
+ constructor() {
3507
+
3508
+ super( 'search' );
3509
+
3510
+ this.events.submit = [];
3511
+ this.events.filter = [];
3512
+
3513
+ const inputDOM = document.createElement( 'input' );
3514
+ inputDOM.placeholder = 'Type here';
3515
+
3516
+ let filter = true;
3517
+ let filterNeedUpdate = true;
3518
+
3519
+ inputDOM.addEventListener( 'focusout', () => {
3520
+
3521
+ filterNeedUpdate = true;
3522
+
3523
+ this.setValue( '' );
3524
+
3525
+ } );
3526
+
3527
+ inputDOM.onkeydown = ( e ) => {
3528
+
3529
+ const keyCode = e.keyCode;
3530
+
3531
+ if ( keyCode === 38 ) {
3532
+
3533
+ const index = this.filteredIndex;
3534
+
3535
+ if ( this.forceAutoComplete ) {
3536
+
3537
+ this.filteredIndex = index !== null ? ( index + 1 ) % ( this.filtered.length || 1 ) : 0;
3538
+
3539
+ } else {
3540
+
3541
+ this.filteredIndex = index !== null ? Math.min( index + 1, this.filtered.length - 1 ) : 0;
3542
+
3543
+ }
3544
+
3545
+ e.preventDefault();
3546
+
3547
+ filter = false;
3548
+
3549
+ } else if ( keyCode === 40 ) {
3550
+
3551
+ const index = this.filteredIndex;
3552
+
3553
+ if ( this.forceAutoComplete ) {
3554
+
3555
+ this.filteredIndex = index - 1;
3556
+
3557
+ if ( this.filteredIndex === null ) this.filteredIndex = this.filtered.length - 1;
3558
+
3559
+ } else {
3560
+
3561
+ this.filteredIndex = index !== null ? index - 1 : null;
3562
+
3563
+ }
3564
+
3565
+ e.preventDefault();
3566
+
3567
+ filter = false;
3568
+
3569
+ } else if ( keyCode === 13 ) {
3570
+
3571
+ this.value = this.currentFiltered ? this.currentFiltered.button.getValue() : inputDOM.value;
3572
+
3573
+ this.submit();
3574
+
3575
+ e.preventDefault();
3576
+
3577
+ filter = false;
3578
+
3579
+ } else {
3580
+
3581
+ filter = true;
3582
+
3583
+ }
3584
+
3585
+ };
3586
+
3587
+ inputDOM.onkeyup = () => {
3588
+
3589
+ if ( filter ) {
3590
+
3591
+ if ( filterNeedUpdate ) {
3592
+
3593
+ this.dispatchEvent( new Event( 'filter' ) );
3594
+
3595
+ filterNeedUpdate = false;
3596
+
3597
+ }
3598
+
3599
+ this.filter( inputDOM.value );
3600
+
3601
+ }
3602
+
3603
+ };
3604
+
3605
+ this.filtered = [];
3606
+ this.currentFiltered = null;
3607
+
3608
+ this.value = '';
3609
+
3610
+ this.forceAutoComplete = false;
3611
+
3612
+ this.dom.append( inputDOM );
3613
+
3614
+ this.inputDOM = inputDOM;
3615
+
3616
+ this.addEventListener( 'filter', ( ) => {
3617
+
3618
+ dispatchEventList( this.events.filter, this );
3619
+
3620
+ } );
3621
+
3622
+ this.addEventListener( 'submit', ( ) => {
3623
+
3624
+ dispatchEventList( this.events.submit, this );
3625
+
3626
+ } );
3023
3627
 
3024
3628
  }
3025
3629
 
3026
- show( x, y ) {
3630
+ submit() {
3027
3631
 
3028
- if ( lastContext !== null ) {
3632
+ this.dispatchEvent( new Event( 'submit' ) );
3029
3633
 
3030
- lastContext.hide();
3634
+ return this.setValue( '' );
3635
+
3636
+ }
3637
+
3638
+ setValue( value ) {
3639
+
3640
+ this.inputDOM.value = value;
3641
+
3642
+ this.filter( value );
3643
+
3644
+ return this;
3645
+
3646
+ }
3647
+
3648
+ getValue() {
3649
+
3650
+ return this.value;
3651
+
3652
+ }
3653
+
3654
+ onFilter( callback ) {
3655
+
3656
+ this.events.filter.push( callback );
3657
+
3658
+ return this;
3659
+
3660
+ }
3661
+
3662
+ onSubmit( callback ) {
3663
+
3664
+ this.events.submit.push( callback );
3665
+
3666
+ return this;
3667
+
3668
+ }
3669
+
3670
+ getFilterByButton( button ) {
3671
+
3672
+ for ( const filter of this.filtered ) {
3673
+
3674
+ if ( filter.button === button ) {
3675
+
3676
+ return filter;
3677
+
3678
+ }
3031
3679
 
3032
3680
  }
3033
3681
 
3034
- lastContext = this;
3682
+ return null;
3683
+
3684
+ }
3685
+
3686
+ add( button ) {
3687
+
3688
+ super.add( button );
3689
+
3690
+ const onDown = ( e ) => {
3691
+
3692
+ const filter = this.getFilterByButton( button );
3035
3693
 
3036
- return super.show( x, y );
3694
+ this.filteredIndex = this.filtered.indexOf( filter );
3695
+ this.value = button.getValue();
3696
+
3697
+ this.submit();
3698
+
3699
+ };
3700
+
3701
+ button.dom.addEventListener( 'mousedown', onDown );
3702
+ button.dom.addEventListener( 'touchstart', onDown );
3703
+
3704
+ this.domButtons.get( button ).remove();
3705
+
3706
+ return this;
3037
3707
 
3038
3708
  }
3039
3709
 
3040
- hide() {
3710
+ set filteredIndex( index ) {
3041
3711
 
3042
- if ( lastContext === this ) {
3712
+ if ( this.currentFiltered ) {
3043
3713
 
3044
- lastContext = null;
3714
+ const buttonDOM = this.domButtons.get( this.currentFiltered.button );
3715
+
3716
+ buttonDOM.classList.remove( 'active' );
3717
+
3718
+ this.currentFiltered = null;
3045
3719
 
3046
3720
  }
3047
3721
 
3048
- return super.hide();
3722
+ const filteredItem = this.filtered[ index ];
3723
+
3724
+ if ( filteredItem ) {
3725
+
3726
+ const buttonDOM = this.domButtons.get( filteredItem.button );
3727
+
3728
+ buttonDOM.classList.add( 'active' );
3729
+
3730
+ this.currentFiltered = filteredItem;
3731
+
3732
+ }
3733
+
3734
+ this.updateFilter();
3049
3735
 
3050
3736
  }
3051
3737
 
3052
- }
3738
+ get filteredIndex() {
3053
3739
 
3054
- class CircleMenu extends Menu {
3740
+ return this.currentFiltered ? this.filtered.indexOf( this.currentFiltered ) : null;
3055
3741
 
3056
- constructor( target = null ) {
3742
+ }
3057
3743
 
3058
- super( 'circle', target );
3744
+ filter( text ) {
3745
+
3746
+ text = filterString( text );
3747
+
3748
+ const filtered = [];
3749
+
3750
+ for ( const button of this.buttons ) {
3751
+
3752
+ const buttonDOM = this.domButtons.get( button );
3753
+
3754
+ buttonDOM.remove();
3755
+
3756
+ const label = filterString( button.getValue() );
3757
+
3758
+ if ( text && label.includes( text ) === true ) {
3759
+
3760
+ const score = text.length / label.length;
3761
+
3762
+ filtered.push( {
3763
+ button,
3764
+ score
3765
+ } );
3766
+
3767
+ }
3768
+
3769
+ }
3770
+
3771
+ filtered.sort( ( a, b ) => b.score - a.score );
3772
+
3773
+ this.filtered = filtered;
3774
+ this.filteredIndex = this.forceAutoComplete ? 0 : null;
3059
3775
 
3060
3776
  }
3061
-
3777
+
3778
+ updateFilter() {
3779
+
3780
+ const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 );
3781
+
3782
+ for ( let i = 0; i < this.filtered.length; i ++ ) {
3783
+
3784
+ const button = this.filtered[ i ].button;
3785
+ const buttonDOM = this.domButtons.get( button );
3786
+
3787
+ buttonDOM.remove();
3788
+
3789
+ if ( i >= filteredIndex ) {
3790
+
3791
+ this.listDOM.append( buttonDOM );
3792
+
3793
+ }
3794
+
3795
+ }
3796
+
3797
+ }
3798
+
3062
3799
  }
3063
3800
 
3064
3801
  class SelectInput extends Input {
3065
3802
 
3066
- constructor( options = [] ) {
3803
+ constructor( options = [], value = null ) {
3067
3804
 
3068
3805
  const dom = document.createElement( 'select' );
3069
3806
  super( dom );
@@ -3074,16 +3811,24 @@ class SelectInput extends Input {
3074
3811
 
3075
3812
  };
3076
3813
 
3077
- this.setOptions( options );
3814
+ dom.onmousedown = dom.ontouchstart = ( e ) => {
3815
+
3816
+ this.dispatchEvent( new Event( 'click' ) );
3817
+
3818
+ };
3819
+
3820
+ this.setOptions( options, value );
3078
3821
 
3079
3822
  }
3080
3823
 
3081
- setOptions( options ) {
3824
+ setOptions( options, value = null ) {
3082
3825
 
3083
3826
  const dom = this.dom;
3827
+ const defaultValue = dom.value;
3084
3828
 
3085
- this.options = options;
3829
+ let containsDefaultValue = false;
3086
3830
 
3831
+ this.options = options;
3087
3832
  dom.innerHTML = '';
3088
3833
 
3089
3834
  for ( let index = 0; index < options.length; index ++ ) {
@@ -3100,10 +3845,18 @@ class SelectInput extends Input {
3100
3845
  option.innerText = opt.name;
3101
3846
  option.value = opt.value;
3102
3847
 
3103
- dom.appendChild( option );
3848
+ if ( containsDefaultValue === false && defaultValue === opt.value ) {
3849
+
3850
+ containsDefaultValue = true;
3851
+
3852
+ }
3853
+
3854
+ dom.append( option );
3104
3855
 
3105
3856
  }
3106
3857
 
3858
+ dom.value = value !== null ? value : containsDefaultValue ? defaultValue : '';
3859
+
3107
3860
  return this;
3108
3861
 
3109
3862
  }
@@ -3126,7 +3879,7 @@ class SelectInput extends Input {
3126
3879
 
3127
3880
  const currentOptions = this.options;
3128
3881
 
3129
- if ( currentOptions.length > 0 ) {
3882
+ if ( currentOptions.length === 0 ) {
3130
3883
 
3131
3884
  this.setOptions( data.options );
3132
3885
 
@@ -3182,6 +3935,8 @@ var Flow = /*#__PURE__*/Object.freeze({
3182
3935
  Menu: Menu,
3183
3936
  ContextMenu: ContextMenu,
3184
3937
  CircleMenu: CircleMenu,
3938
+ Tips: Tips,
3939
+ Search: Search,
3185
3940
  DraggableElement: DraggableElement,
3186
3941
  LabelElement: LabelElement,
3187
3942
  TitleElement: TitleElement,
@@ -3291,6 +4046,12 @@ class Loader extends EventTarget {
3291
4046
 
3292
4047
  const Class = lib && lib[ obj.type ] ? lib[ obj.type ] : Flow[ obj.type ];
3293
4048
 
4049
+ if ( ! Class ) {
4050
+
4051
+ console.error( `Class "${ obj.type }" not found!` );
4052
+
4053
+ }
4054
+
3294
4055
  objects[ id ] = new Class();
3295
4056
 
3296
4057
  }
@@ -3335,4 +4096,4 @@ class Loader extends EventTarget {
3335
4096
  Loader.DEFAULT = 'default';
3336
4097
  Loader.OBJECTS = 'objects';
3337
4098
 
3338
- export { ButtonInput, Canvas, CircleMenu, ColorInput, ContextMenu, DraggableElement, Element, Input, LabelElement, Loader, Menu, Node, NumberInput, ObjectNode, PanelNode, REVISION, SelectInput, Serializer, SliderInput, StringInput, Styles, TextInput, TitleElement, ToggleInput, Utils };
4099
+ export { ButtonInput, Canvas, CircleMenu, ColorInput, ContextMenu, DraggableElement, Element, Input, LabelElement, Loader, Menu, Node, NumberInput, ObjectNode, PanelNode, REVISION, Search, SelectInput, Serializer, SliderInput, StringInput, Styles, TextInput, Tips, TitleElement, ToggleInput, Utils };