├── .gitignore ├── .gitattributes ├── extraParams.hxml ├── bin ├── index.html └── controls.html ├── submit.sh ├── src └── sui │ ├── controls │ ├── TextControl.hx │ ├── PasswordControl.hx │ ├── DateSelectControl.hx │ ├── TextSelectControl.hx │ ├── NumberSelectControl.hx │ ├── TelControl.hx │ ├── SearchControl.hx │ ├── DateControl.hx │ ├── DateTimeControl.hx │ ├── BoolControl.hx │ ├── UrlControl.hx │ ├── EmailControl.hx │ ├── IntControl.hx │ ├── FloatControl.hx │ ├── ControlValues.hx │ ├── ControlStreams.hx │ ├── IControl.hx │ ├── IntRangeControl.hx │ ├── FloatRangeControl.hx │ ├── DataList.hx │ ├── ColorControl.hx │ ├── BaseTextControl.hx │ ├── NumberControl.hx │ ├── LabelControl.hx │ ├── NumberRangeControl.hx │ ├── MultiControl.hx │ ├── TimeControl.hx │ ├── ChoiceControl.hx │ ├── Options.hx │ ├── TriggerControl.hx │ ├── BaseDateControl.hx │ ├── SingleInputControl.hx │ ├── SelectControl.hx │ ├── DoubleInputControl.hx │ ├── ArrayControl.hx │ └── MapControl.hx │ ├── macro │ └── Embed.hx │ ├── components │ └── Grid.hx │ └── Sui.hx ├── haxelib.json ├── package.json ├── style ├── icons │ ├── remove.svg │ ├── add.svg │ ├── down.svg │ ├── expand.svg │ ├── up.svg │ └── collapse.svg └── sui.styl ├── demo ├── DemoObject.hx └── DemoControls.hx ├── LICENSE ├── gulpfile.js ├── README.md └── css └── sui.css /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-* 2 | node_modules -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | bin/* linguist-vendored 2 | css/* linguist-vendored 3 | -------------------------------------------------------------------------------- /extraParams.hxml: -------------------------------------------------------------------------------- 1 | -lib dots 2 | -lib thx.stream 3 | -lib thx.stream.dom 4 | -D sui_embed_css=1 -------------------------------------------------------------------------------- /bin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SUI - Simple User Interface 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /submit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | rm -f sui.zip 4 | zip -r sui.zip src test css/sui.css extraParams.hxml haxelib.json LICENSE README.md -x "*/\.*" 5 | haxelib submit sui.zip -x 6 | -------------------------------------------------------------------------------- /bin/controls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SUI - Simple User Interface 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /src/sui/controls/TextControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class TextControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | super(value, "text", "text", options); 8 | } 9 | } -------------------------------------------------------------------------------- /src/sui/controls/PasswordControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class PasswordControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | super(value, "text", "password", options); 8 | } 9 | } -------------------------------------------------------------------------------- /src/sui/controls/DateSelectControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class DateSelectControl extends SelectControl { 6 | public function new(defaultValue : Date, options : OptionsSelect) { 7 | super(defaultValue, "select-date", options); 8 | } 9 | } -------------------------------------------------------------------------------- /src/sui/controls/TextSelectControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class TextSelectControl extends SelectControl { 6 | public function new(defaultValue : String, options : OptionsSelect) { 7 | super(defaultValue, "select-text", options); 8 | } 9 | } -------------------------------------------------------------------------------- /src/sui/controls/NumberSelectControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class NumberSelectControl extends SelectControl { 6 | public function new(defaultValue : T, options : OptionsSelect) { 7 | super(defaultValue, "select-number", options); 8 | } 9 | } -------------------------------------------------------------------------------- /src/sui/controls/TelControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class TelControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | if(null == options) 8 | options = {}; 9 | super(value, "tel", "tel", options); 10 | } 11 | } -------------------------------------------------------------------------------- /src/sui/controls/SearchControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class SearchControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | if(null == options) 8 | options = {}; 9 | super(value, "search", "search", options); 10 | } 11 | } -------------------------------------------------------------------------------- /src/sui/controls/DateControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using StringTools; 4 | import sui.controls.Options; 5 | using thx.Nulls; 6 | using thx.Strings; 7 | 8 | class DateControl extends BaseDateControl { 9 | public function new(value : Date, ?options : OptionsDate) { 10 | super(value, "date", "date", BaseDateControl.toRFCDate, options); 11 | } 12 | } -------------------------------------------------------------------------------- /src/sui/controls/DateTimeControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using StringTools; 4 | import sui.controls.Options; 5 | using thx.Nulls; 6 | using thx.Strings; 7 | 8 | class DateTimeControl extends BaseDateControl { 9 | public function new(value : Date, ?options : OptionsDate) { 10 | super(value, "date-time", "datetime-local", BaseDateControl.toRFCDateTimeNoSeconds, options); 11 | } 12 | } -------------------------------------------------------------------------------- /src/sui/controls/BoolControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | class BoolControl extends SingleInputControl { 4 | public function new(value : Bool, ?options : Options) { 5 | super(value, "change", "bool", "checkbox", options); 6 | } 7 | 8 | override function setInput(v : Bool) 9 | input.checked = v; 10 | 11 | override function getInput() : Bool 12 | return input.checked; 13 | } -------------------------------------------------------------------------------- /src/sui/controls/UrlControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class UrlControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | if(null == options) 8 | options = {}; 9 | if(null == options.placeholder) 10 | options.placeholder = "http://example.com"; 11 | super(value, "url", "url", options); 12 | } 13 | } -------------------------------------------------------------------------------- /src/sui/controls/EmailControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class EmailControl extends BaseTextControl { 6 | public function new(value : String, ?options : OptionsText) { 7 | if(null == options) 8 | options = {}; 9 | if(null == options.placeholder) 10 | options.placeholder = "name@example.com"; 11 | super(value, "email", "email", options); 12 | } 13 | } -------------------------------------------------------------------------------- /src/sui/controls/IntControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class IntControl extends NumberControl { 6 | public function new(value : Int, ?options : OptionsNumber) { 7 | super(value, "int", options); 8 | } 9 | 10 | override function setInput(v : Int) 11 | input.value = '$v'; 12 | 13 | override function getInput() : Int 14 | return Std.parseInt(input.value); 15 | } -------------------------------------------------------------------------------- /src/sui/controls/FloatControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class FloatControl extends NumberControl { 6 | public function new(value : Float, ?options : OptionsNumber) { 7 | super(value, "float", options); 8 | } 9 | 10 | override function setInput(v : Float) 11 | input.value = '$v'; 12 | 13 | override function getInput() : Float 14 | return Std.parseFloat(input.value); 15 | } -------------------------------------------------------------------------------- /src/sui/controls/ControlValues.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using thx.stream.Value; 4 | 5 | class ControlValues { 6 | public var value(default, null) : Value; 7 | public var focused(default, null) : Value; 8 | public var enabled(default, null) : Value; 9 | 10 | public function new(defaultValue : T) { 11 | value = new Value(defaultValue); 12 | focused = new Value(false); 13 | enabled = new Value(true); 14 | } 15 | } -------------------------------------------------------------------------------- /src/sui/macro/Embed.hx: -------------------------------------------------------------------------------- 1 | package sui.macro; 2 | 3 | import haxe.macro.Context; 4 | import thx.macro.Macros; 5 | 6 | class Embed { 7 | macro public static function file(path : String) { 8 | var suiPath = Macros.getModulePath("sui.Sui").split("/").slice(0, -2).join("/")+"/../"; 9 | path = suiPath + path; 10 | Context.registerModuleDependency("sui.Sui", path); 11 | var content = sys.io.File.getContent(path); 12 | return macro $v{content}; 13 | } 14 | } -------------------------------------------------------------------------------- /src/sui/controls/ControlStreams.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using thx.stream.Emitter; 4 | 5 | class ControlStreams { 6 | public var value(default, null) : Emitter; 7 | public var focused(default, null) : Emitter; 8 | public var enabled(default, null) : Emitter; 9 | public function new(value : Emitter, focused : Emitter, enabled : Emitter) { 10 | this.value = value; 11 | this.focused = focused; 12 | this.enabled = enabled; 13 | } 14 | } -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sui", 3 | "url": "https://github.com/fponticelli/sui", 4 | "license": "MIT", 5 | "classPath": "src", 6 | "tags": ["ui", "js"], 7 | "description": "Fixes and updated package for thx.core", 8 | "releasenote": "Fixed compatibility issues with thx.core and some improvements.", 9 | "version": "0.5.0", 10 | "contributors": ["fponticelli"], 11 | "dependencies": { 12 | "thx.core": "", 13 | "thx.promise": "", 14 | "thx.stream": "", 15 | "thx.stream.dom": "", 16 | "dots": "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/sui/controls/IControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.DOMElement as Element; 4 | 5 | interface IControl { 6 | public var el(default, null) : Element; 7 | public var defaultValue(default, null) : T; 8 | public var streams(default, null) : ControlStreams; 9 | 10 | public function set(v : T) : Void; 11 | public function get() : T; 12 | 13 | public function isEnabled() : Bool; 14 | public function isFocused() : Bool; 15 | 16 | public function disable() : Void; 17 | public function enable() : Void; 18 | 19 | public function focus() : Void; 20 | public function blur() : Void; 21 | 22 | public function reset() : Void; 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sui", 3 | "version": "0.0.1", 4 | "description": "Simple User Interface", 5 | "main": "gulpfile.js", 6 | "bin": { 7 | "sui": "sui.js" 8 | }, 9 | "directories": { 10 | "test": "test" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "author": "Franco Ponticelli", 16 | "license": "MIT", 17 | "dependencies": { 18 | "canvas": "^1.2.2", 19 | "stylus": "^0.51.1" 20 | }, 21 | "devDependencies": { 22 | "gulp": "^3.8.11", 23 | "gulp-connect": "^2.2.0", 24 | "gulp-stylus": "^2.0.1", 25 | "nib": "^1.1.0", 26 | "run-sequence": "^1.1.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/sui/controls/IntRangeControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | using thx.Ints; 5 | 6 | class IntRangeControl extends NumberRangeControl { 7 | public function new(value : Int, ?options : OptionsNumber) { 8 | if(null == options) 9 | options = {}; 10 | 11 | if(null == options.min) 12 | options.min = Ints.min(value, 0); 13 | if(null == options.min) { 14 | var s = null != options.step ? options.step : 100; 15 | options.max = Ints.max(value, s); 16 | } 17 | super(value, options); 18 | } 19 | 20 | override function getInput1() : Int 21 | return input1.value.canParse() ? input1.value.parse() : null; 22 | 23 | override function getInput2() : Int 24 | return input2.value.canParse() ? input2.value.parse() : null; 25 | } -------------------------------------------------------------------------------- /src/sui/controls/FloatRangeControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | using thx.Floats; 5 | 6 | class FloatRangeControl extends NumberRangeControl { 7 | public function new(value : Float, ?options : OptionsNumber) { 8 | if(null == options) 9 | options = {}; 10 | 11 | if(null == options.min) 12 | options.min = Math.min(value, 0); 13 | if(null == options.min) { 14 | var s = null != options.step ? options.step : 1; 15 | options.max = Math.max(value, s); 16 | } 17 | super(value, options); 18 | } 19 | 20 | override function getInput1() : Float 21 | return input1.value.canParse() ? input1.value.parse() : null; 22 | 23 | override function getInput2() : Float 24 | return input2.value.canParse() ? input2.value.parse() : null; 25 | } -------------------------------------------------------------------------------- /style/icons/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/sui/controls/DataList.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.DOMElement as Element; 4 | import dots.Html; 5 | 6 | class DataList { 7 | public static var nid = 0; 8 | public var id : String; 9 | 10 | public static function fromArray(container : Element, values : Array) 11 | return new DataList(container, values.map(function(v) return { value : v, label : v })); 12 | 13 | public function new(container : Element, values : Array<{ label : String, value : String }>) { 14 | id = 'sui-dl-${++nid}'; 15 | var datalist = Html.parse(''); 16 | container.appendChild(datalist); 17 | } 18 | 19 | static function toOption(o : { label : String, value : String }) 20 | return ''; 21 | 22 | public function applyTo(el : Element) { 23 | el.setAttribute("list", id); 24 | return this; 25 | } 26 | } -------------------------------------------------------------------------------- /style/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /style/icons/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /style/icons/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /style/icons/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /style/icons/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /demo/DemoObject.hx: -------------------------------------------------------------------------------- 1 | import haxe.ds.Option; 2 | import sui.Sui; 3 | 4 | class DemoObject { 5 | public static function main() { 6 | var demoObject = new DemoObject(), 7 | sui = new Sui(); 8 | 9 | sui.bind(demoObject); 10 | sui.attach(); 11 | } 12 | /* 13 | public var scores : Array; 14 | public var name : String; 15 | public var startedOn : Date; 16 | public var info : Map; 17 | */ 18 | public var option : Option; 19 | // @:isVar public var enabled(get, set) : Bool; 20 | 21 | public function new() { 22 | /* 23 | this.scores = [1, 5, 10]; 24 | this.name = "Sui"; 25 | this.startedOn = Date.fromString("2014-11-09"); 26 | this.info = ["a" => "b"]; 27 | */ 28 | this.option = None; 29 | } 30 | 31 | public function traceMe() 32 | trace(Reflect.fields(this) 33 | .map(function(field) return '$field: ${Reflect.field(this, field)}') 34 | .join(", ")); 35 | /* 36 | function get_enabled() 37 | return enabled; 38 | 39 | function set_enabled(v : Bool) { 40 | trace('enabled set to $v'); 41 | return enabled = v; 42 | } 43 | */ 44 | } -------------------------------------------------------------------------------- /src/sui/controls/ColorControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class ColorControl extends DoubleInputControl { 6 | static var PATTERN = ~/^[#][0-9a-f]{6}$/i; 7 | public function new(value : String, ?options : OptionsColor) { 8 | if(null == options) 9 | options = {}; 10 | 11 | super(value, "color", "input", "color", "input", "text", PATTERN.match, options); 12 | 13 | if(null != options.autocomplete) 14 | input2.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 15 | 16 | if(null != options.list) 17 | new DataList(el, options.list).applyTo(input1).applyTo(input2); 18 | else if(null != options.values) 19 | DataList.fromArray(el, options.values).applyTo(input1).applyTo(input2); 20 | 21 | setInputs(value); 22 | } 23 | 24 | override function setInput1(v : String) 25 | input1.value = v; 26 | 27 | override function setInput2(v : String) 28 | input2.value = v; 29 | 30 | override function getInput1() : String 31 | return input1.value; 32 | 33 | override function getInput2() : String 34 | return input2.value; 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Franco Ponticelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/sui/controls/BaseTextControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | 5 | class BaseTextControl extends SingleInputControl { 6 | public function new(value : String, name : String, type : String, ?options : OptionsText) { 7 | if(null == options) 8 | options = {}; 9 | super(value, "input", name, type, options); 10 | 11 | if(null != options.maxlength) 12 | input.setAttribute('maxlength', '${options.maxlength}'); 13 | if(null != options.autocomplete) 14 | input.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 15 | if(null != options.pattern) 16 | input.setAttribute('pattern', '${options.pattern}'); 17 | if(null != options.placeholder) 18 | input.setAttribute('placeholder', '${options.placeholder}'); 19 | if(null != options.list) 20 | new DataList(el, options.list).applyTo(input); 21 | else if(null != options.values) 22 | DataList.fromArray(el, options.values).applyTo(input); 23 | } 24 | 25 | override function setInput(v : String) 26 | input.value = v; 27 | 28 | override function getInput() : String 29 | return input.value; 30 | } -------------------------------------------------------------------------------- /src/sui/controls/NumberControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using StringTools; 4 | import sui.controls.Options; 5 | 6 | class NumberControl extends SingleInputControl { 7 | public function new(value : T, name : String, ?options : OptionsNumber) { 8 | if(null == options) 9 | options = {}; 10 | super(value, "input", name, "number", options); 11 | 12 | if(null != options.autocomplete) 13 | input.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 14 | 15 | if(null != options.min) 16 | input.setAttribute('min', '${options.min}'); 17 | if(null != options.max) 18 | input.setAttribute('max', '${options.max}'); 19 | if(null != options.step) 20 | input.setAttribute('step', '${options.step}'); 21 | if(null != options.placeholder) 22 | input.setAttribute('placeholder', '${options.placeholder}'); 23 | 24 | if(null != options.list) 25 | new DataList(el, options.list.map(function(o) { 26 | return { 27 | label : o.label, 28 | value : '${o.value}' 29 | }; 30 | })).applyTo(input); 31 | else if(null != options.values) 32 | new DataList(el, options.values.map(function(o) { 33 | return { 34 | label : '${o}', 35 | value : '${o}' 36 | }; 37 | })).applyTo(input); 38 | } 39 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | seq = require('run-sequence'), 3 | connect = require('gulp-connect'); 4 | exec = require('child_process').exec, 5 | url = require('stylus').url, 6 | stylus = require('gulp-stylus'), 7 | nib = require('nib'); 8 | 9 | gulp.task('connect', function () { 10 | connect.server({ 11 | root: ['bin'], 12 | port: 8000, 13 | livereload: true 14 | }); 15 | }); 16 | 17 | gulp.task('html', function () { 18 | gulp.src('./bin/*.html') 19 | .pipe(connect.reload()); 20 | }); 21 | 22 | gulp.task('js', function () { 23 | gulp.src('./bin/*.js') 24 | .pipe(connect.reload()); 25 | }); 26 | 27 | gulp.task('build', function(cb) { 28 | seq("stylus", "haxe", cb); 29 | }); 30 | 31 | gulp.task('haxe', function(cb) { 32 | exec("haxe build.hxml", function(err, stdout, stderr) { 33 | cb(err); 34 | }); 35 | }); 36 | 37 | gulp.task('stylus', function() { 38 | return gulp.src('./style/sui.styl') 39 | .pipe(stylus({ 40 | use: [nib()], 41 | compress: true, 42 | url: 'embedurl' 43 | })) 44 | .pipe(gulp.dest('./css')); 45 | }); 46 | 47 | gulp.task('watch', function () { 48 | gulp.watch(['./style/icons/*.svg'], ['build'] ); 49 | gulp.watch(['./bin/*.html'], ['html']); 50 | gulp.watch(['./bin/controls.js'], ['js']); 51 | gulp.watch(['./style/*.styl'], ['build']); 52 | }); 53 | 54 | gulp.task('default', function (cb) { 55 | seq(['connect'], ['build'], ['watch'], cb); 56 | }); 57 | -------------------------------------------------------------------------------- /src/sui/components/Grid.hx: -------------------------------------------------------------------------------- 1 | package sui.components; 2 | 3 | import dots.Html; 4 | import dots.Query; 5 | import js.html.DOMElement; 6 | import sui.controls.IControl; 7 | using thx.Arrays; 8 | using thx.Functions; 9 | 10 | class Grid { 11 | public var el(default, null) : DOMElement; 12 | 13 | public function new() { 14 | el = Html.parse('
'); 15 | } 16 | 17 | public function add(cell : CellContent) switch cell { 18 | case Single(control): 19 | var container = Html.parse(''); 20 | Query.first("td", container).appendChild(control.el); 21 | el.appendChild(container); 22 | case HorizontalPair(left, right): 23 | var container = Html.parse(''); 24 | Query.first(".sui-left", container).appendChild(left.el); 25 | Query.first(".sui-right", container).appendChild(right.el); 26 | el.appendChild(container); 27 | case VerticalPair(top, bottom): 28 | var containers = Html.parseArray(''); 29 | Query.first("td", containers[0]).appendChild(top.el); 30 | Query.first("td", containers[1]).appendChild(bottom.el); 31 | containers.map.fn(el.appendChild(_)); 32 | } 33 | } 34 | 35 | typedef WithElement = { 36 | public var el(default, null) : DOMElement; 37 | } 38 | 39 | enum CellContent { 40 | Single(control : WithElement); 41 | VerticalPair(top : WithElement, bottom : WithElement); 42 | HorizontalPair(left : WithElement, right : WithElement); 43 | } 44 | -------------------------------------------------------------------------------- /src/sui/controls/LabelControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import dots.Html; 4 | import dots.Query; 5 | import js.html.DOMElement as Element; 6 | 7 | class LabelControl implements IControl { 8 | public var el(default, null) : Element; 9 | public var output(default, null) : Element; 10 | public var defaultValue(default, null) : String; 11 | public var streams(default, null) : ControlStreams; 12 | 13 | var values : ControlValues; 14 | 15 | public function new(defaultValue : String) { 16 | var template = '
$defaultValue
'; 17 | 18 | this.defaultValue = defaultValue; 19 | 20 | values = new ControlValues(defaultValue); 21 | streams = new ControlStreams(values.value, values.focused, values.enabled); 22 | 23 | el = Html.parse(template); 24 | output = Query.first("output", el); 25 | 26 | values.enabled.subscribe(function(v) if(v) { 27 | el.classList.add("sui-disabled"); 28 | } else { 29 | el.classList.remove("sui-disabled"); 30 | }); 31 | } 32 | 33 | public function set(v : String) { 34 | output.innerHTML = v; 35 | values.value.set(v); 36 | } 37 | 38 | public function get() : String 39 | return values.value.get(); 40 | 41 | public function isEnabled() 42 | return values.enabled.get(); 43 | 44 | public function isFocused() 45 | return values.focused.get(); 46 | 47 | public function disable() 48 | values.enabled.set(false); 49 | 50 | public function enable() 51 | values.enabled.set(true); 52 | 53 | public function focus() {} 54 | 55 | public function blur() {} 56 | 57 | public function reset() 58 | set(defaultValue); 59 | } -------------------------------------------------------------------------------- /src/sui/controls/NumberRangeControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import sui.controls.Options; 4 | using thx.Floats; 5 | 6 | class NumberRangeControl extends DoubleInputControl { 7 | public function new(value : T, ?options : OptionsNumber) { 8 | super(value, "float-range", "input", "range", "input", "number", function(v) return v != null, options); 9 | 10 | if(null != options.autocomplete) { 11 | input1.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 12 | input2.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 13 | } 14 | 15 | if(null != options.min) { 16 | input1.setAttribute('min', '${options.min}'); 17 | input2.setAttribute('min', '${options.min}'); 18 | } 19 | if(null != options.max) { 20 | input1.setAttribute('max', '${options.max}'); 21 | input2.setAttribute('max', '${options.max}'); 22 | } 23 | if(null != options.step) { 24 | input1.setAttribute('step', '${options.step}'); 25 | input2.setAttribute('step', '${options.step}'); 26 | } 27 | if(null != options.placeholder) 28 | input2.setAttribute('placeholder', '${options.placeholder}'); 29 | 30 | if(null != options.list) 31 | new DataList(el, options.list.map(function(o) { 32 | return { 33 | label : o.label, 34 | value : '${o.value}' 35 | }; 36 | })).applyTo(input1).applyTo(input2); 37 | else if(null != options.values) 38 | new DataList(el, options.values.map(function(o) { 39 | return { 40 | label : '${o}', 41 | value : '${o}' 42 | }; 43 | })).applyTo(input1).applyTo(input2); 44 | 45 | setInputs(value); 46 | } 47 | 48 | override function setInput1(v : T) 49 | input1.value = '$v'; 50 | 51 | override function setInput2(v : T) 52 | input2.value = '$v'; 53 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SUI 2 | ## Simple User Interface: Html + Haxe 3 | 4 | Example: 5 | 6 | ```haxe 7 | var ui = new sui.Sui(); 8 | ui.bool("boolean", function(v) trace('bool: $v')); 9 | ui.color("color", { 10 | list : [ 11 | { value : "#FF0000", label : "red" }, 12 | { value : "#00FF00", label : "blue" }, 13 | { value : "#0000FF", label : "green" }] 14 | }, function(v) trace('color: $v')); 15 | ui.int("int range", 20, { 16 | min : 10, 17 | max : 30 18 | }, function(v) trace('int range: $v')); 19 | ui.trigger("trigger", function() trace("triggered")); 20 | ui.attach(); 21 | ``` 22 | 23 | [A live sample of some of the available controls](https://rawgit.com/fponticelli/sui/master/bin/controls.html). 24 | ## Installation 25 | SUI is available on haxelib (http://lib.haxe.org/p/sui/) 26 | 27 | To install, run: 28 | ```haxelib install sui``` 29 | 30 | ##### Dependencies 31 | SUI dependents on _[thx.core](https://github.com/fponticelli/thx.core), [thx.promise](https://github.com/fponticelli/thx.promise), [thx.stream](https://github.com/fponticelli/thx.stream), [thx.stream.dom](https://github.com/fponticelli/thx.stream.dom), [dots](https://github.com/fponticelli/dots)_ 32 | 33 | ## TODO 34 | 35 | ### API 36 | * Sui.add(): macro automap field/variable to control 37 | * append to container (with position and close controls) 38 | * Sui.hide()/Sui.show() (with default keyboard control H) 39 | * Sui.open()/Sui.close() 40 | * presets? save/restore? 41 | * listen? 42 | 43 | ### Controls 44 | 45 | * folder (with open/collapse) 46 | * select string (options) 47 | * select float 48 | * select int 49 | * select date 50 | * text area 51 | * objects and nested objects 52 | * arrays 53 | * unstructured objects (create field together with values) 54 | 55 | 56 | ### Inspiration 57 | 58 | [dat-gui](http://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage) 59 | -------------------------------------------------------------------------------- /src/sui/controls/MultiControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.DOMElement as Element; 4 | using thx.stream.Emitter; 5 | using thx.Arrays; 6 | using thx.Nulls; 7 | using thx.Functions; 8 | 9 | class MultiControl implements IControl { 10 | public var el(default, null) : Element; 11 | public var defaultValue(default, null) : T; 12 | public var streams(default, null) : ControlStreams; 13 | 14 | var controls : Array>; 15 | var enabled : Bool; 16 | var values : ControlValues; 17 | public function new(defaultValue : T, element : Element, controls : Array>) { 18 | this.el = element; 19 | this.defaultValue = defaultValue; 20 | this.controls = controls; 21 | values = new ControlValues(defaultValue); 22 | streams = new ControlStreams(values.value, values.focused, values.enabled); 23 | controls.map.fn(_.streams.focused.feed(values.focused)); 24 | 25 | streams.enabled.subscribe(function(value) { 26 | controls.map.fn(value ? _.enable() : _.disable()); 27 | }); 28 | } 29 | 30 | public function set(v : T) : Void 31 | values.value.set(v); 32 | 33 | public function get() : T 34 | return values.value.get(); 35 | 36 | public function isEnabled() : Bool 37 | return values.enabled.get(); 38 | 39 | public function isFocused() : Bool 40 | return values.focused.get(); 41 | 42 | public function disable() : Void 43 | values.enabled.set(false); 44 | 45 | public function enable() : Void 46 | values.enabled.set(true); 47 | 48 | public function focus() : Void 49 | controls.first().with(_.focus()); 50 | 51 | public function blur() : Void { 52 | var el = js.Browser.document.activeElement; 53 | controls 54 | // TODO el could just contain _.el 55 | .filter.fn(_.el == el) 56 | .first().with(el.blur()); 57 | } 58 | 59 | public function reset() : Void 60 | set(defaultValue); 61 | } 62 | -------------------------------------------------------------------------------- /src/sui/controls/TimeControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using StringTools; 4 | import sui.controls.Options; 5 | 6 | class TimeControl extends SingleInputControl { 7 | public function new(value : Float, ?options : OptionsTime) { 8 | if(null == options) 9 | options = {}; 10 | super(value, "input", "time", "time", options); 11 | 12 | if(null != options.autocomplete) 13 | input.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 14 | 15 | if(null != options.min) 16 | input.setAttribute('min', timeToString(options.min)); 17 | if(null != options.max) 18 | input.setAttribute('max', timeToString(options.max)); 19 | 20 | if(null != options.list) 21 | new DataList(el, options.list.map(function(o) { 22 | return { 23 | label : o.label, 24 | value : timeToString(o.value) 25 | }; 26 | })).applyTo(input); 27 | else if(null != options.values) 28 | new DataList(el, options.values.map(function(o) { 29 | return { 30 | label : timeToString(o), 31 | value : timeToString(o) 32 | }; 33 | })).applyTo(input); 34 | } 35 | 36 | public static function timeToString(t : Float) { 37 | var h = Math.floor(t / 3600000); 38 | t -= h * 3600000; 39 | var m = Math.floor(t / 60000); 40 | t -= m * 60000; 41 | var s = t / 1000, 42 | hh = '$h'.lpad("0", 2), 43 | mm = '$m'.lpad("0", 2), 44 | ss = (s >= 10 ? '' : '0') + s; 45 | return '$hh:$mm:$ss'; 46 | } 47 | 48 | public static function stringToTime(t : String) : Float { 49 | var p = t.split(":"), 50 | h = Std.parseInt(p[0]), 51 | m = Std.parseInt(p[1]), 52 | s = Std.parseFloat(p[2]); 53 | return s * 1000 + m * 60000 + h * 3600000; 54 | } 55 | 56 | override function setInput(v : Float) 57 | input.value = timeToString(v); 58 | 59 | override function getInput() : Float 60 | return stringToTime(input.value); 61 | } -------------------------------------------------------------------------------- /src/sui/controls/ChoiceControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.DOMElement as Element; 4 | using thx.stream.Emitter; 5 | using thx.Arrays; 6 | using thx.Functions; 7 | using thx.Nulls; 8 | 9 | class ChoiceControl implements IControl { 10 | public var el(default, null) : Element; 11 | public var defaultValue(default, null) : T; 12 | public var streams(default, null) : ControlStreams; 13 | 14 | var controls : Array>; 15 | var enabled : Bool; 16 | var values : ControlValues; 17 | public function new(defaultValue : T, getSelectValue T -> TSelect, createSelectControl : TSelect -> IControl, createControls : TSelect -> ) { 18 | this.el = element; 19 | this.defaultValue = defaultValue; 20 | this.controls = controls; 21 | values = new ControlValues(defaultValue); 22 | streams = new ControlStreams(values.value, values.focused, values.enabled); 23 | controls.map.fn(_.streams.focused.feed(values.focused)); 24 | 25 | streams.enabled.subscribe(function(value) { 26 | controls.map.fn(value ? _.enable() : _.disable()); 27 | }); 28 | } 29 | 30 | public function set(v : T) : Void 31 | values.value.set(v); 32 | 33 | public function get() : T 34 | return values.value.get(); 35 | 36 | public function isEnabled() : Bool 37 | return values.enabled.get(); 38 | 39 | public function isFocused() : Bool 40 | return values.focused.get(); 41 | 42 | public function disable() : Void 43 | values.enabled.set(false); 44 | 45 | public function enable() : Void 46 | values.enabled.set(true); 47 | 48 | public function focus() : Void 49 | controls.first().with(_.focus()); 50 | 51 | public function blur() : Void { 52 | var el = js.Browser.document.activeElement; 53 | controls 54 | // TODO el could just contain _.el 55 | .filter.fn(_.el == el) 56 | .first().with(el.blur()); 57 | } 58 | 59 | public function reset() : Void 60 | set(defaultValue); 61 | } 62 | -------------------------------------------------------------------------------- /src/sui/controls/Options.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | typedef Options = { 4 | ?disabled : Bool, 5 | ?autofocus : Bool, 6 | ?allownull : Bool 7 | } 8 | 9 | typedef OptionsList = {> Options, 10 | ?list : Array<{ label : String, value : T }>, 11 | ?values : Array 12 | } 13 | 14 | typedef OptionsSelect = {> OptionsList, 15 | ?labelfornull : String 16 | } 17 | 18 | typedef OptionsColor = {> OptionsList, 19 | ?autocomplete : Bool 20 | } 21 | 22 | typedef OptionsDate = {> OptionsList, 23 | ?min : Date, 24 | ?max : Date, 25 | ?autocomplete : Bool 26 | } 27 | 28 | typedef OptionsKindDate = {> OptionsDate, 29 | ?kind : DateKind, 30 | ?labelfornull : String, 31 | ?listonly : Bool 32 | } 33 | 34 | enum DateKind { 35 | DateOnly; 36 | DateTime; 37 | } 38 | 39 | typedef OptionsNumber = {> OptionsList, 40 | ?min : T, 41 | ?max : T, 42 | ?step : T, 43 | ?autocomplete : Bool, 44 | ?placeholder : String 45 | } 46 | 47 | typedef OptionsKindFloat = {> OptionsNumber, 48 | ?kind : FloatKind, 49 | ?labelfornull : String, 50 | ?listonly : Bool 51 | } 52 | 53 | enum FloatKind { 54 | FloatNumber; 55 | FloatTime; 56 | } 57 | 58 | typedef OptionsKindInt = {> OptionsNumber, 59 | ?labelfornull : String, 60 | ?listonly : Bool 61 | } 62 | 63 | typedef OptionsText = {> OptionsList, 64 | ?maxlength : Int, 65 | ?autocomplete : Bool, 66 | ?pattern : String, 67 | ?placeholder : String 68 | } 69 | 70 | typedef OptionsKindText = {> OptionsText, 71 | ?kind : TextKind, 72 | ?labelfornull : String, 73 | ?listonly : Bool 74 | } 75 | 76 | enum TextKind { 77 | TextEmail; 78 | TextPassword; 79 | TextSearch; 80 | TextTel; 81 | PlainText; 82 | TextUrl; 83 | } 84 | 85 | typedef OptionsTime = {> Options, 86 | ?min : Float, 87 | ?max : Float, 88 | ?autocomplete : Bool, 89 | ?list : Array<{ label : String, value : Float }>, 90 | ?values : Array 91 | } 92 | 93 | typedef OptionsFolder = { 94 | ?collapsible : Bool, 95 | ?collapsed : Bool 96 | } -------------------------------------------------------------------------------- /src/sui/controls/TriggerControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import dots.Html; 4 | import dots.Query; 5 | import js.html.DOMElement as Element; 6 | import js.html.ButtonElement; 7 | import thx.Nil; 8 | using thx.stream.dom.Dom; 9 | 10 | class TriggerControl implements IControl { 11 | public var el(default, null) : Element; 12 | public var button(default, null) : ButtonElement; 13 | public var defaultValue(default, null) : Nil; 14 | public var streams(default, null) : ControlStreams; 15 | 16 | var values : ControlValues; 17 | 18 | public function new(label : String, ?options : Options) { 19 | var template = '
'; 20 | 21 | if(null == options) 22 | options = {}; 23 | 24 | defaultValue = nil; 25 | 26 | el = Html.parse(template); 27 | button = (cast Query.first('button', el) : ButtonElement); 28 | 29 | values = new ControlValues(nil); 30 | var emitter = button.streamClick().toNil(); 31 | streams = new ControlStreams(emitter, values.focused, values.enabled); 32 | 33 | values.enabled.subscribe(function(v) if(v) { 34 | el.classList.add("sui-disabled"); 35 | button.removeAttribute("disabled"); 36 | } else { 37 | el.classList.remove("sui-disabled"); 38 | button.setAttribute("disabled", "disabled"); 39 | }); 40 | 41 | values.focused.subscribe(function(v) if(v) { 42 | el.classList.add("sui-focused"); 43 | } else { 44 | el.classList.remove("sui-focused"); 45 | }); 46 | 47 | button.streamFocus().feed(values.focused); 48 | 49 | if(options.autofocus) 50 | focus(); 51 | if(options.disabled) 52 | disable(); 53 | } 54 | 55 | public function set(v : Nil) 56 | button.click(); 57 | 58 | public function get() : Nil 59 | return nil; 60 | 61 | public function isEnabled() 62 | return values.enabled.get(); 63 | 64 | public function isFocused() 65 | return values.focused.get(); 66 | 67 | public function disable() 68 | values.enabled.set(false); 69 | 70 | public function enable() 71 | values.enabled.set(true); 72 | 73 | public function focus() 74 | button.focus(); 75 | 76 | public function blur() 77 | button.blur(); 78 | 79 | public function reset() 80 | set(defaultValue); 81 | } -------------------------------------------------------------------------------- /src/sui/controls/BaseDateControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | using StringTools; 4 | import sui.controls.Options; 5 | using thx.Nulls; 6 | using thx.Strings; 7 | 8 | class BaseDateControl extends SingleInputControl { 9 | var dateToString : Date -> String; 10 | public function new(value : Date, name : String, type : String, dateToString : Date -> String, ?options : OptionsDate) { 11 | if(null == options) 12 | options = {}; 13 | 14 | this.dateToString = dateToString; 15 | 16 | super(value, "input", name, type, options); 17 | 18 | if(null != options.autocomplete) 19 | input.setAttribute('autocomplete', options.autocomplete ? 'on' : 'off'); 20 | 21 | if(null != options.min) 22 | input.setAttribute('min', dateToString(options.min)); 23 | if(null != options.max) 24 | input.setAttribute('max', dateToString(options.max)); 25 | 26 | if(null != options.list) 27 | new DataList(el, options.list.map(function(o) { 28 | return { 29 | label : o.label, 30 | value : dateToString(o.value) 31 | }; 32 | })).applyTo(input); 33 | else if(null != options.values) 34 | new DataList(el, options.values.map(function(o) { 35 | return { 36 | label : o.toString(), 37 | value : dateToString(o) 38 | }; 39 | })).applyTo(input); 40 | } 41 | 42 | override function setInput(v : Date) 43 | input.value = dateToString(v); 44 | 45 | override function getInput() : Date 46 | return input.value.isEmpty() ? null : fromRFC(input.value); 47 | 48 | public static function toRFCDate(date : Date) { 49 | var y = date.getFullYear(), 50 | m = '${date.getMonth()+1}'.lpad('0', 2), 51 | d = '${date.getDate()}'.lpad('0', 2); 52 | return '$y-$m-$d'; 53 | } 54 | 55 | public static function toRFCDateTime(date : Date) { 56 | var d = toRFCDate(date), 57 | hh = '${date.getHours()}'.lpad('0', 2), 58 | mm = '${date.getMinutes()}'.lpad('0', 2), 59 | ss = '${date.getSeconds()}'.lpad('0', 2); 60 | return '${d}T$hh:$mm:$ss'; 61 | } 62 | 63 | public static function toRFCDateTimeNoSeconds(date : Date) { 64 | var d = toRFCDate(date), 65 | hh = '${date.getHours()}'.lpad('0', 2), 66 | mm = '${date.getMinutes()}'.lpad('0', 2); 67 | return '${d}T$hh:$mm:00'; 68 | } 69 | 70 | public static function fromRFC(date : String) { 71 | var dp = date.split("T")[0], 72 | dt = (date.split("T")[1]).or("00:00:00"), 73 | p = dp.split("-"), 74 | y = Std.parseInt(p[0]), 75 | m = Std.parseInt(p[1])-1, 76 | d = Std.parseInt(p[2]), 77 | t = dt.split(":"), 78 | hh = Std.parseInt(t[0]), 79 | mm = Std.parseInt(t[1]), 80 | ss = Std.parseInt(t[2]); 81 | return new Date(y, m, d, hh, mm, ss); 82 | } 83 | } -------------------------------------------------------------------------------- /src/sui/controls/SingleInputControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import dots.Html; 4 | import dots.Query; 5 | import js.html.DOMElement as Element; 6 | import js.html.InputElement; 7 | import thx.error.AbstractMethod; 8 | using thx.stream.dom.Dom; 9 | 10 | class SingleInputControl implements IControl { 11 | public var el(default, null) : Element; 12 | public var input(default, null) : InputElement; 13 | public var defaultValue(default, null) : T; 14 | public var streams(default, null) : ControlStreams; 15 | 16 | var values : ControlValues; 17 | 18 | public function new(defaultValue : T, event : String, name : String, type : String, options : Options) { 19 | var template = '
'; 20 | 21 | if(null == options) 22 | options = {}; 23 | if(null == options.allownull) 24 | options.allownull = true; 25 | 26 | this.defaultValue = defaultValue; 27 | 28 | values = new ControlValues(defaultValue); 29 | streams = new ControlStreams(values.value, values.focused, values.enabled); 30 | 31 | el = Html.parse(template); 32 | input = (cast Query.first('input', el) : InputElement); 33 | 34 | values.enabled.subscribe(function(v) if(v) { 35 | el.classList.add("sui-disabled"); 36 | input.removeAttribute("disabled"); 37 | } else { 38 | el.classList.remove("sui-disabled"); 39 | input.setAttribute("disabled", "disabled"); 40 | }); 41 | 42 | values.focused.subscribe(function(v) if(v) { 43 | el.classList.add("sui-focused"); 44 | } else { 45 | el.classList.remove("sui-focused"); 46 | }); 47 | 48 | setInput(defaultValue); 49 | 50 | input.streamFocus().feed(values.focused); 51 | input.streamEvent(event) 52 | .map(function(_) return getInput()) 53 | .feed(values.value); 54 | 55 | if(!options.allownull) 56 | input.setAttribute("required", "required"); 57 | if(options.autofocus) 58 | focus(); 59 | if(options.disabled) 60 | disable(); 61 | } 62 | 63 | function setInput(v : T) 64 | throw new AbstractMethod(); 65 | 66 | function getInput() : T 67 | return throw new AbstractMethod(); 68 | 69 | public function set(v : T) { 70 | setInput(v); 71 | values.value.set(v); 72 | } 73 | 74 | public function get() : T 75 | return values.value.get(); 76 | 77 | public function isEnabled() 78 | return values.enabled.get(); 79 | 80 | public function isFocused() 81 | return values.focused.get(); 82 | 83 | public function disable() 84 | values.enabled.set(false); 85 | 86 | public function enable() 87 | values.enabled.set(true); 88 | 89 | public function focus() 90 | input.focus(); 91 | 92 | public function blur() 93 | input.blur(); 94 | 95 | public function reset() 96 | set(defaultValue); 97 | } 98 | -------------------------------------------------------------------------------- /src/sui/controls/SelectControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.DOMElement as Element; 4 | import js.html.SelectElement; 5 | import dots.Html; 6 | import dots.Query; 7 | import sui.controls.Options; 8 | using thx.Arrays; 9 | using thx.Nulls; 10 | using thx.stream.dom.Dom; 11 | using thx.Functions; 12 | 13 | class SelectControl implements IControl { 14 | public var el(default, null) : Element; 15 | public var select(default, null) : SelectElement; 16 | public var defaultValue(default, null) : T; 17 | public var streams(default, null) : ControlStreams; 18 | 19 | var options : Array; 20 | var values : ControlValues; 21 | var count : Int = 0; 22 | 23 | function new(defaultValue : T, name : String, options : OptionsSelect) { 24 | var template = '
'; 25 | if(null == options) 26 | throw " A select control requires an option object with values or list set"; 27 | if(null == options.values && null == options.list) 28 | throw " A select control requires either the values or list option"; 29 | if(null == options.allownull) 30 | options.allownull = false; 31 | this.defaultValue = defaultValue; 32 | 33 | values = new ControlValues(defaultValue); 34 | streams = new ControlStreams(values.value, values.focused, values.enabled); 35 | 36 | el = Html.parse(template); 37 | select = (cast Query.first('select', el) : SelectElement); 38 | 39 | values.enabled.subscribe(function(v) if(v) { 40 | el.classList.add("sui-disabled"); 41 | select.removeAttribute("disabled"); 42 | } else { 43 | el.classList.remove("sui-disabled"); 44 | select.setAttribute("disabled", "disabled"); 45 | }); 46 | 47 | values.focused.subscribe(function(v) if(v) { 48 | el.classList.add("sui-focused"); 49 | } else { 50 | el.classList.remove("sui-focused"); 51 | }); 52 | 53 | this.options = []; 54 | 55 | (options.allownull ? [{ label : (options.labelfornull).or("- none -"), value : null }] : []).concat( 56 | (options.list) 57 | .or(options.values.map.fn({ value : _, label : Std.string(_) }))) 58 | .map.fn(addOption(_.label, _.value)); 59 | 60 | setInput(defaultValue); 61 | 62 | select.streamFocus().feed(values.focused); 63 | select.streamEvent("change") 64 | .map(function(_) return getInput()) 65 | .feed(values.value); 66 | 67 | if(options.autofocus) 68 | focus(); 69 | if(options.disabled) 70 | disable(); 71 | } 72 | 73 | public function addOption(label : String, value : T) { 74 | var index = count++, 75 | option = Html.parse(''); 76 | options[index] = value; 77 | select.appendChild(option); 78 | return option; 79 | } 80 | 81 | function setInput(v : T) { 82 | var index = options.indexOf(v); 83 | if(index < 0) throw 'value "$v" is not included in this select control'; 84 | select.selectedIndex = index; 85 | } 86 | 87 | function getInput() { 88 | return options[select.selectedIndex]; 89 | } 90 | 91 | public function set(v : T) { 92 | setInput(v); 93 | values.value.set(v); 94 | } 95 | 96 | public function get() : T 97 | return values.value.get(); 98 | 99 | public function isEnabled() 100 | return values.enabled.get(); 101 | 102 | public function isFocused() 103 | return values.focused.get(); 104 | 105 | public function disable() 106 | values.enabled.set(false); 107 | 108 | public function enable() 109 | values.enabled.set(true); 110 | 111 | public function focus() 112 | select.focus(); 113 | 114 | public function blur() 115 | select.blur(); 116 | 117 | public function reset() 118 | set(defaultValue); 119 | } 120 | -------------------------------------------------------------------------------- /src/sui/controls/DoubleInputControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import dots.Html; 4 | import dots.Query; 5 | import js.html.DOMElement as Element; 6 | import js.html.InputElement; 7 | import thx.error.AbstractMethod; 8 | using thx.stream.dom.Dom; 9 | import dots.Detect; 10 | 11 | class DoubleInputControl implements IControl { 12 | public var el(default, null) : Element; 13 | public var input1(default, null) : InputElement; 14 | public var input2(default, null) : InputElement; 15 | public var defaultValue(default, null) : T; 16 | public var streams(default, null) : ControlStreams; 17 | 18 | var values : ControlValues; 19 | 20 | public function new(defaultValue : T, name : String, event1 : String, type1 : String, event2 : String, type2 : String, filter : T -> Bool, options : Options) { 21 | var template = '
'; 22 | 23 | if(null == options) 24 | options = {}; 25 | if(null == options.allownull) 26 | options.allownull = true; 27 | 28 | this.defaultValue = defaultValue; 29 | 30 | values = new ControlValues(defaultValue); 31 | streams = new ControlStreams(values.value, values.focused, values.enabled); 32 | 33 | el = Html.parse(template); 34 | input1 = (cast Query.first('.input1', el) : InputElement); 35 | input2 = (cast Query.first('.input2', el) : InputElement); 36 | 37 | values.enabled.subscribe(function(v) if(v) { 38 | el.classList.add("sui-disabled"); 39 | input1.removeAttribute("disabled"); 40 | input2.removeAttribute("disabled"); 41 | } else { 42 | el.classList.remove("sui-disabled"); 43 | input1.setAttribute("disabled", "disabled"); 44 | input2.setAttribute("disabled", "disabled"); 45 | }); 46 | 47 | values.focused.subscribe(function(v) if(v) { 48 | el.classList.add("sui-focused"); 49 | } else { 50 | el.classList.remove("sui-focused"); 51 | }); 52 | 53 | input1.streamFocus() 54 | .merge(input2.streamFocus()) 55 | .feed(values.focused); 56 | input1.streamEvent(event1) 57 | .map(function(_) return getInput1()) 58 | .subscribe(function(v) { 59 | setInput2(v); 60 | values.value.set(v); 61 | }); 62 | input2.streamEvent(event2) 63 | .map(function(_) return getInput2()) 64 | .filter(filter) 65 | .subscribe(function(v) { 66 | setInput1(v); 67 | values.value.set(v); 68 | }); 69 | 70 | if(!options.allownull) { 71 | input1.setAttribute("required", "required"); 72 | input2.setAttribute("required", "required"); 73 | } 74 | if(options.autofocus) 75 | focus(); 76 | if(options.disabled) 77 | disable(); 78 | 79 | if(!Detect.supportsInput(type1)) 80 | input1.style.display = "none"; 81 | } 82 | 83 | function setInputs(v : T) { 84 | setInput1(v); 85 | setInput2(v); 86 | } 87 | 88 | function setInput1(v : T) 89 | throw new AbstractMethod(); 90 | 91 | function setInput2(v : T) 92 | throw new AbstractMethod(); 93 | 94 | function getInput1() : T 95 | return throw new AbstractMethod(); 96 | 97 | function getInput2() : T 98 | return throw new AbstractMethod(); 99 | 100 | public function set(v : T) { 101 | setInputs(v); 102 | values.value.set(v); 103 | } 104 | 105 | public function get() : T 106 | return values.value.get(); 107 | 108 | public function isEnabled() 109 | return values.enabled.get(); 110 | 111 | public function isFocused() 112 | return values.focused.get(); 113 | 114 | public function disable() 115 | values.enabled.set(false); 116 | 117 | public function enable() 118 | values.enabled.set(true); 119 | 120 | public function focus() 121 | input2.focus(); 122 | 123 | public function blur() { 124 | var el = js.Browser.document.activeElement; 125 | if(el == input1 || el == input2) 126 | el.blur(); 127 | } 128 | 129 | public function reset() 130 | set(defaultValue); 131 | } 132 | -------------------------------------------------------------------------------- /src/sui/controls/ArrayControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.ButtonElement; 4 | import js.html.DOMElement as Element; 5 | import sui.controls.Options; 6 | import dots.Html; 7 | import dots.Query; 8 | using thx.stream.dom.Dom; 9 | using thx.stream.Emitter; 10 | using thx.Arrays; 11 | using thx.Functions; 12 | using thx.Nulls; 13 | 14 | class ArrayControl implements IControl> { 15 | public var el(default, null) : Element; 16 | public var ul(default, null) : Element; 17 | public var addButton(default, null) : Element; 18 | public var defaultValue(default, null) : Array; 19 | public var defaultElementValue(default, null) : T; 20 | public var streams(default, null) : ControlStreams>; 21 | public var createElementControl(default, null) : T -> IControl; 22 | public var length(default, null) : Int; 23 | 24 | var values : ControlValues>; 25 | var elements : Array<{ 26 | control : IControl, 27 | el : Element, 28 | index : Int 29 | }>; 30 | 31 | public function new(defaultValue : Array, defaultElementValue : T, createElementControl : T -> IControl, ?options : Options) { 32 | var template = '
33 |
    34 |
    35 |
    '; 36 | options = (options).or({}); 37 | this.defaultValue = defaultValue; 38 | this.defaultElementValue = defaultElementValue; 39 | this.createElementControl = createElementControl; 40 | this.elements = []; 41 | length = 0; 42 | 43 | values = new ControlValues(defaultValue); 44 | streams = new ControlStreams(values.value, values.focused.debounce(0), values.enabled); 45 | 46 | el = Html.parse(template); 47 | ul = Query.first('ul', el); 48 | addButton = Query.first('.sui-icon-add', el); 49 | 50 | addButton.streamClick().subscribe(function(_) addControl(defaultElementValue)); 51 | 52 | values.enabled.subscribe(function(v) if(v) { 53 | el.classList.add("sui-disabled"); 54 | } else { 55 | el.classList.remove("sui-disabled"); 56 | }); 57 | 58 | values.focused.subscribe(function(v) if(v) { 59 | el.classList.add("sui-focused"); 60 | } else { 61 | el.classList.remove("sui-focused"); 62 | }); 63 | 64 | values.enabled 65 | .negate() 66 | .subscribe(el.subscribeToggleClass("sui-disabled")); 67 | 68 | values.enabled.subscribe(function(v) { 69 | elements.map.fn(v ? _.control.enable() : _.control.disable()); 70 | }); 71 | 72 | setValue(defaultValue); 73 | 74 | reset(); 75 | 76 | if(options.autofocus) 77 | focus(); 78 | if(options.disabled) 79 | disable(); 80 | } 81 | 82 | function addControl(value : T) { 83 | var o = { 84 | control : createElementControl(value), 85 | el : Html.parse('
  • 86 |
    87 |
    88 |
    89 |
  • '), 90 | index : length++ 91 | }; 92 | 93 | ul.appendChild(o.el); 94 | 95 | var removeElement = Query.first(".sui-icon-remove", o.el), 96 | upElement = Query.first(".sui-icon-up", o.el), 97 | downElement = Query.first(".sui-icon-down", o.el), 98 | controlContainer = Query.first(".sui-control-container", o.el); 99 | 100 | controlContainer.appendChild(o.control.el); 101 | 102 | removeElement.streamClick().subscribe(function(_) { 103 | ul.removeChild(o.el); 104 | elements.splice(o.index, 1); 105 | for(i in o.index...elements.length) 106 | elements[i].index--; 107 | length--; 108 | updateValue(); 109 | }); 110 | 111 | elements.push(o); 112 | o.control.streams.value.subscribe(function(_) updateValue()); 113 | o.control.streams 114 | .focused 115 | .subscribe(o.el.subscribeToggleClass("sui-focus")); 116 | 117 | o.control.streams 118 | .focused 119 | .feed(values.focused); 120 | 121 | upElement.streamClick() 122 | .subscribe(function(_) { 123 | var pos = o.index, 124 | prev = elements[pos-1]; 125 | elements[pos] = prev; 126 | elements[pos-1] = o; 127 | prev.index = pos; 128 | o.index = pos - 1; 129 | ul.insertBefore(o.el, prev.el); 130 | updateValue(); 131 | }); 132 | 133 | downElement.streamClick() 134 | .subscribe(function(_) { 135 | var pos = o.index, 136 | next = elements[pos+1]; 137 | elements[pos] = next; 138 | elements[pos+1] = o; 139 | next.index = pos; 140 | o.index = pos + 1; 141 | ul.insertBefore(next.el, o.el); 142 | updateValue(); 143 | }); 144 | } 145 | 146 | function setValue(v : Array) 147 | v.map.fn(addControl(_)); 148 | 149 | function getValue() 150 | return elements.map.fn(_.control.get()); 151 | 152 | function updateValue() 153 | values.value.set(getValue()); 154 | 155 | public function set(v : Array) { 156 | clear(); 157 | setValue(v); 158 | values.value.set(v); 159 | } 160 | 161 | public function get() : Array 162 | return values.value.get(); 163 | 164 | public function isEnabled() 165 | return values.enabled.get(); 166 | 167 | public function isFocused() 168 | return values.focused.get(); 169 | 170 | public function disable() 171 | values.enabled.set(false); 172 | 173 | public function enable() 174 | values.enabled.set(true); 175 | 176 | public function focus() 177 | if(elements.length > 0) 178 | elements.last().control.focus(); 179 | 180 | public function blur() { 181 | var el = js.Browser.document.activeElement; 182 | elements 183 | // TODO el could just contain _.control.el 184 | .filter.fn(_.control.el == el) 185 | .first().with(el.blur()); 186 | } 187 | 188 | public function reset() 189 | set(defaultValue); 190 | 191 | function clear() { 192 | length = 0; 193 | elements.map(function(item) { 194 | //control.destroy(); 195 | ul.removeChild(item.el); 196 | }); 197 | elements = []; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/sui/controls/MapControl.hx: -------------------------------------------------------------------------------- 1 | package sui.controls; 2 | 3 | import js.html.ButtonElement; 4 | import js.html.DOMElement as Element; 5 | import sui.controls.Options; 6 | import dots.Html; 7 | import dots.Query; 8 | using thx.stream.dom.Dom; 9 | using thx.stream.Emitter; 10 | using thx.Arrays; 11 | using thx.Iterators; 12 | using thx.Nulls; 13 | using thx.Functions; 14 | 15 | class MapControl implements IControl> { 16 | public var el(default, null) : Element; 17 | public var tbody(default, null) : Element; 18 | public var addButton(default, null) : Element; 19 | public var defaultValue(default, null) : Map; 20 | public var streams(default, null) : ControlStreams>; 21 | public var createMap(default, null) : Void -> Map; 22 | public var createKeyControl(default, null) : TKey -> IControl; 23 | public var createValueControl(default, null) : TValue -> IControl; 24 | public var length(default, null) : Int; 25 | 26 | var values : ControlValues>; 27 | var elements : Array<{ 28 | controlKey : IControl, 29 | controlValue : IControl, 30 | el : Element, 31 | index : Int 32 | }>; 33 | 34 | public function new(?defaultValue : Map, createMap : Void -> Map, createKeyControl : TKey -> IControl, createValueControl : TValue -> IControl, ?options : Options) { 35 | var template = '
    36 |
    37 |
    38 |
    '; 39 | options = (options).or({}); 40 | if(null == defaultValue) 41 | defaultValue = createMap(); 42 | this.defaultValue = defaultValue; 43 | this.createMap = createMap; 44 | this.createKeyControl = createKeyControl; 45 | this.createValueControl = createValueControl; 46 | this.elements = []; 47 | length = 0; 48 | 49 | values = new ControlValues(defaultValue); 50 | streams = new ControlStreams(values.value, values.focused.debounce(0), values.enabled); 51 | 52 | el = Html.parse(template); 53 | tbody = Query.first('tbody', el); 54 | addButton = Query.first('.sui-icon-add', el); 55 | 56 | addButton.streamClick().subscribe(function(_) addControl(null, null)); 57 | 58 | values.enabled.subscribe(function(v) if(v) { 59 | el.classList.add("sui-disabled"); 60 | } else { 61 | el.classList.remove("sui-disabled"); 62 | }); 63 | 64 | values.focused.subscribe(function(v) if(v) { 65 | el.classList.add("sui-focused"); 66 | } else { 67 | el.classList.remove("sui-focused"); 68 | }); 69 | 70 | values.enabled 71 | .negate() 72 | .subscribe(el.subscribeToggleClass("sui-disabled")); 73 | 74 | values.enabled.subscribe(function(v) { 75 | elements.map.fn(if(v){ 76 | _.controlKey.enable(); 77 | _.controlValue.enable(); 78 | } else { 79 | _.controlKey.disable(); 80 | _.controlValue.disable(); 81 | }); 82 | }); 83 | 84 | setValue(defaultValue); 85 | 86 | reset(); 87 | 88 | if(options.autofocus) 89 | focus(); 90 | if(options.disabled) 91 | disable(); 92 | } 93 | 94 | function addControl(key : TKey, value : TValue) { 95 | var o = { 96 | controlKey : createKeyControl(key), 97 | controlValue : createValueControl(value), 98 | el : Html.parse(' 99 | 100 | 101 | 102 | '), 103 | index : length++ 104 | }; 105 | 106 | tbody.appendChild(o.el); 107 | 108 | var removeElement = Query.first(".sui-icon-remove", o.el), 109 | controlKeyContainer = Query.first(".sui-map-key", o.el), 110 | controlValueContainer = Query.first(".sui-map-value", o.el); 111 | 112 | controlKeyContainer.appendChild(o.controlKey.el); 113 | controlValueContainer.appendChild(o.controlValue.el); 114 | 115 | removeElement.streamClick().subscribe(function(_) { 116 | tbody.removeChild(o.el); 117 | elements.splice(o.index, 1); 118 | for(i in o.index...elements.length) 119 | elements[i].index--; 120 | length--; 121 | updateValue(); 122 | }); 123 | 124 | elements.push(o); 125 | 126 | o.controlKey.streams.value 127 | .toNil() 128 | .merge(o.controlValue.streams.value.toNil()) 129 | .subscribe(function(_) updateValue()); 130 | 131 | o.controlKey.streams 132 | .focused 133 | .merge(o.controlValue.streams.focused) 134 | .subscribe(o.el.subscribeToggleClass("sui-focus")); 135 | 136 | o.controlKey.streams 137 | .focused 138 | .merge(o.controlValue.streams.focused) 139 | .feed(values.focused); 140 | } 141 | 142 | function setValue(v : Map) 143 | v.keys().map.fn(addControl(_, v.get(_))); 144 | 145 | function getValue() : Map { 146 | var map = createMap(); 147 | elements.map(function(o) { 148 | var k = o.controlKey.get(), 149 | v = o.controlValue.get(); 150 | if(k == null || map.exists(k)) { 151 | o.controlKey.el.classList.add("sui-invalid"); 152 | return; 153 | } 154 | o.controlKey.el.classList.remove("sui-invalid"); 155 | map.set(k, v); 156 | }); 157 | return map; 158 | } 159 | 160 | function updateValue() 161 | values.value.set(getValue()); 162 | 163 | public function set(v : Map) { 164 | clear(); 165 | setValue(v); 166 | values.value.set(v); 167 | } 168 | 169 | public function get() : Map 170 | return values.value.get(); 171 | 172 | public function isEnabled() 173 | return values.enabled.get(); 174 | 175 | public function isFocused() 176 | return values.focused.get(); 177 | 178 | public function disable() 179 | values.enabled.set(false); 180 | 181 | public function enable() 182 | values.enabled.set(true); 183 | 184 | public function focus() 185 | if(elements.length > 0) 186 | elements.last().controlValue.focus(); 187 | 188 | public function blur() { 189 | var el = js.Browser.document.activeElement; 190 | elements 191 | .filter.fn(_.controlKey.el == el || _.controlValue.el == el) 192 | .first().with(el.blur()); 193 | } 194 | 195 | public function reset() 196 | set(defaultValue); 197 | 198 | function clear() { 199 | length = 0; 200 | elements.map(function(item) { 201 | //control.destroy(); 202 | tbody.removeChild(item.el); 203 | }); 204 | elements = []; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /demo/DemoControls.hx: -------------------------------------------------------------------------------- 1 | using dots.Html; 2 | using dots.Query; 3 | 4 | import sui.Sui; 5 | import sui.components.*; 6 | import sui.controls.*; 7 | import sui.controls.MapControl; 8 | import sui.controls.Options; 9 | 10 | import js.Browser; 11 | 12 | class DemoControls { 13 | public static function main() { 14 | var ui = new Sui(); 15 | /* 16 | ui.choice("chose one?", function(option : String) return switch option { 17 | case "a": 18 | var sui = new Sui(); 19 | sui.bool("bool", function(_){}); 20 | sui.text("text", function(_){}); 21 | return sui; 22 | case "b": 23 | var sui = new Sui(); 24 | sui.int("number", { min : 0, max : 10 }, function(_){}); 25 | return sui; 26 | case _: null; 27 | }, [ 28 | { value : "a", label : "A" }, 29 | { value : "b", label : "B" }, 30 | { value : "c", label : "C" } 31 | ]); 32 | */ 33 | ui.bool("boolean", function(v) trace('bool: $v')); 34 | ui.date("date time", { 35 | kind : DateTime, 36 | list : [{ label : "birthday", value : Date.fromString("1972-05-02 16:01:00") }, { label : "other", value : Date.fromString("1974-06-09") }, { label : "today", value : Date.now() }] 37 | }, function(v) trace('date time: $v')); 38 | ui.date("date", { 39 | list : [{ label : "birthday", value : Date.fromString("1972-05-02") }, { label : "today", value : Date.now() }] 40 | }, function(v) trace('date: $v')); 41 | ui.text("email", "", { 42 | kind : TextEmail, 43 | }, function(v) trace('email: $v')); 44 | ui.text("secret", "", { 45 | kind : TextPassword, 46 | placeholder : "shhh" 47 | }, function(v) trace('password: $v')); 48 | ui.text("text", "", { 49 | placeholder : "placeholder" 50 | }, function(v) trace('string: $v')); 51 | ui.text(null, "", { 52 | placeholder : "libs", 53 | values : ["haxe", "thx", "sui"] 54 | }, function(v) trace('string: $v')); 55 | ui.float("time", 3600000 * 23, { 56 | kind : FloatTime, 57 | values : [0, 60000, 3600000] 58 | }, function(t) trace('time: $t')); 59 | ui.color("color", { 60 | list : [{ value : "#FF0000", label : "red" }, { value : "#00FF00", label : "blue" }, { value : "#0000FF", label : "green" }] 61 | }, function(v) trace('color: $v')); 62 | ui.float("float", function(v) trace('float: $v')); 63 | ui.float("float range", 0.5, { 64 | step : 0.01, 65 | min : 0.0, 66 | max : 1.0, 67 | values : [0, 0.5, 1] 68 | }, function(v) trace('float range: $v')); 69 | ui.int("int", { 70 | list : [{ label : "one", value : 1}, { label : "two", value : 2}, { label : "three", value : 3}] 71 | }, function(v) trace('int: $v')); 72 | ui.int("int range", 20, { 73 | min : 10, 74 | max : 30 75 | }, function(v) trace('int range: $v')); 76 | ui.label("temp").set("hello there"); 77 | ui.text("search", "", { 78 | kind : TextSearch 79 | }, function(v) trace('search: $v')); 80 | ui.text("tel", "", { 81 | kind : TextTel 82 | }, function(v) trace('tel: $v')); 83 | ui.trigger("trigger", function() trace("triggered")); 84 | ui.text("url", "", { 85 | kind : TextUrl 86 | }, function(v) trace('url: $v')); 87 | 88 | var sui = ui.folder("more details"), 89 | obj = { 90 | name : "Sui", 91 | info : { 92 | age : 0.1 93 | } 94 | }; 95 | sui.bind(obj.name); 96 | sui.bind(obj.info.age); 97 | 98 | var a = 12; 99 | sui.bind(a); 100 | 101 | ui.attach(); 102 | 103 | var grid = new Grid(); 104 | Browser.document.body.appendChild(grid.el); 105 | grid.add(Single(new LabelControl("I act like a title"))); 106 | grid.add(HorizontalPair(new LabelControl("got it?"), new BoolControl(true))); 107 | grid.add(VerticalPair(new LabelControl("name"), new TextControl("sui"))); 108 | 109 | createControlContainer(new MapControl([ 110 | 1 => "thx", 111 | 3 => "sui", 112 | 4 => "haxe" 113 | ], 114 | function() return new Map(), 115 | function(key) return new IntControl(key), 116 | function(value) return new TextControl(value))); 117 | 118 | createControlContainer(new ArrayControl([1,2,3], 119 | 5, 120 | function(value) return new IntRangeControl(value, { min : 0, max : 10 }))); 121 | createControlContainer(new ArrayControl(["a", "b", "c"], 122 | "", 123 | function(value) return new TextControl(value))); 124 | 125 | var createInnerArrayControl = function(v) { 126 | return new ArrayControl( 127 | v, 128 | "", 129 | function(v) return new TextControl(v), 130 | {} 131 | ); 132 | }; 133 | 134 | createControlContainer(new ArrayControl( 135 | [["a", "b", "c"], ["a", "b"], ["a"]], 136 | ["x", "y"], 137 | createInnerArrayControl, 138 | {})); 139 | 140 | createControlContainer(new TextSelectControl("sui", { values : ["thx", "sui", "haxe"], allownull : true })); 141 | createControlContainer(new NumberSelectControl(3, { values : [1,2,3,4,5,6]})); 142 | createControlContainer(new NumberSelectControl(0.3, { values : [0.1,0.2,0.3,0.4,0.5,0.6]})); 143 | 144 | createControlContainer(new LabelControl("just a label, not interactive")); 145 | createControlContainer(new TriggerControl("click me")); 146 | createControlContainer(new ColorControl("#ff0000")); 147 | createControlContainer(new BoolControl(true)); 148 | createControlContainer(new TextControl(null, { placeholder : "put text here" })); 149 | createControlContainer(new FloatControl(7.7)); 150 | createControlContainer(new IntControl(7)); 151 | createControlContainer(new FloatRangeControl(7, { min : 0, max : 100, step : 0.01 })); 152 | createControlContainer(new IntRangeControl(7, { min : 0, max : 100 })); 153 | } 154 | 155 | public static function createControlContainer(control : IControl) { 156 | var description = Type.getClassName(Type.getClass(control)).split(".").pop(), 157 | el = Html.parse('
    158 |

    $description

    159 |
    160 |
    161 |
    162 |
    '); 163 | Browser.document.getElementById("container").appendChild(el); 164 | var container = Query.first(".container", el), 165 | focus = Query.first(".focus", el), 166 | value = Query.first(".value", el); 167 | container.appendChild(control.el); 168 | 169 | control.streams.value.subscribe(function(v) { 170 | value.textContent = 'value: $v'; 171 | }); 172 | 173 | control.streams.focused.subscribe(function(v) { 174 | focus.textContent = 'focus: $v'; 175 | }); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /style/sui.styl: -------------------------------------------------------------------------------- 1 | $light-border = #ddd 2 | $dark-border = #aaa 3 | $white-background = #ffffff 4 | $light-background = #f6f6f6 5 | $very-light-background = #fafafa 6 | $dark-background = #eeeeee 7 | $invalid = #dd0000 8 | $font-default = Helvetica, "Nimbus Sans L", "Liberation Sans", Arial, sans-serif 9 | $font-mono = Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace 10 | $outline-color = #eee solid 2px 11 | $outline-offset = -2px 12 | $icon = 12px 13 | $iconmini = 8px 14 | 15 | box-shadow(args...) 16 | -webkit-box-shadow args 17 | -moz-box-shadow args 18 | box-shadow args 19 | 20 | // icons 21 | .sui-control 22 | i.sui-icon-remove 23 | background-image embedurl("icons/remove.svg") 24 | i.sui-icon-add 25 | background-image embedurl("icons/add.svg") 26 | i.sui-icon-up 27 | background-image embedurl("icons/up.svg") 28 | i.sui-icon-down 29 | background-image embedurl("icons/down.svg") 30 | 31 | // components 32 | .sui-grid 33 | * 34 | box-sizing border-box 35 | border-collapse collapse 36 | td 37 | border-bottom 1px solid $light-border 38 | margin 0 39 | padding 0 40 | tr:first-child 41 | td 42 | border-top 1px solid $light-border 43 | td:first-child 44 | border-left 1px solid $light-border 45 | td:last-child 46 | border-right 1px solid $light-border 47 | td.sui-top 48 | td.sui-left 49 | background-color $white-background 50 | td.sui-bottom 51 | td.sui-right 52 | background-color $light-background 53 | 54 | .sui-bottom-left 55 | .sui-bottom-right 56 | .sui-top-left 57 | .sui-top-right 58 | position absolute 59 | background-color #fff 60 | 61 | .sui-top-right 62 | top 0 63 | right 0 64 | box-shadow(-1px 1px 6px rgba(0,0,0,0.1)) 65 | &.sui-grid 66 | tr:first-child 67 | td 68 | border-top none 69 | td:last-child 70 | border-right none 71 | .sui-top-left 72 | top 0 73 | left 0 74 | box-shadow(1px 1px 6px rgba(0,0,0,0.1)) 75 | &.sui-grid 76 | tr:first-child 77 | td 78 | border-top none 79 | td:last-child 80 | border-left none 81 | .sui-bottom-right 82 | bottom 0 83 | right 0 84 | box-shadow(-1px 1px 6px rgba(0,0,0,0.1)) 85 | &.sui-grid 86 | tr:first-child 87 | td 88 | border-bottom none 89 | td:last-child 90 | border-right none 91 | .sui-bottom-left 92 | bottom 0 93 | left 0 94 | box-shadow(1px 1px 6px rgba(0,0,0,0.1)) 95 | &.sui-grid 96 | tr:first-child 97 | td 98 | border-bottom none 99 | td:last-child 100 | border-left none 101 | .sui-fill 102 | position absolute 103 | width 100% 104 | max-height 100% 105 | top 0 106 | left 0 107 | 108 | .sui-append 109 | width 100% 110 | 111 | .sui-control 112 | .sui-folder 113 | -moz-user-select -moz-none 114 | -khtml-user-select none 115 | -webkit-user-select none 116 | -o-user-select none 117 | user-select none 118 | font-size 11px 119 | font-family $font-default 120 | line-height 18px 121 | vertical-align middle 122 | * 123 | box-sizing border-box 124 | margin 0 125 | padding 0 126 | button 127 | line-height 18px 128 | vertical-align middle 129 | input 130 | line-height 18px 131 | vertical-align middle 132 | border none 133 | background-color $light-background 134 | max-width 16em 135 | button:hover 136 | background-color $very-light-background 137 | border 1px solid $light-border 138 | button:focus 139 | background-color $very-light-background 140 | border 1px solid $dark-border 141 | outline $outline-color 142 | input:focus 143 | outline $outline-color 144 | $outline-offset $outline-offset 145 | background-color $very-light-background 146 | 147 | output 148 | padding 0 6px 149 | background-color $white-background 150 | display inline-block 151 | 152 | input[type="number"] 153 | input[type="date"] 154 | input[type="datetime-local"] 155 | input[type="time"] 156 | text-align right 157 | input[type="number"] 158 | font-family $font-mono 159 | input 160 | padding 0 6px 161 | input[type="color"] 162 | input[type="checkbox"] 163 | padding 0 164 | margin 0 165 | input[type="range"] 166 | margin 0 8px 167 | min-height 19px 168 | button 169 | background-color $dark-background 170 | border 1px solid $dark-border 171 | border-radius 4px 172 | 173 | &.sui-control-single 174 | input 175 | output 176 | button 177 | select 178 | width 100% 179 | input[type="checkbox"] 180 | width initial 181 | &.sui-control-double 182 | input 183 | output 184 | button 185 | select 186 | width 50% 187 | .input1 188 | width calc(100% - 7em) 189 | max-width 8em 190 | .input2 191 | width 7em 192 | .input1[type="range"] 193 | width calc(100% - 7em - 16px) 194 | &.sui-type-bool 195 | text-align center 196 | &.sui-invalid 197 | border-left 4px solid $invalid 198 | 199 | .sui-array 200 | list-style none 201 | .sui-array-item 202 | border-bottom 1px dotted $dark-border 203 | position relative 204 | .sui-icon 205 | .sui-icon-mini 206 | opacity 0.1 207 | .sui-array-add 208 | .sui-icon 209 | .sui-icon-mini 210 | opacity 0.2 211 | > * 212 | vertical-align top 213 | &:first-child 214 | > .sui-move 215 | > .sui-icon-up 216 | visibility hidden 217 | &:last-child 218 | border-bottom none 219 | > .sui-move 220 | > .sui-icon-down 221 | visibility hidden 222 | > div 223 | display inline-block 224 | .sui-move 225 | position absolute 226 | width $iconmini 227 | height 100% 228 | .sui-icon-mini 229 | display block 230 | position absolute 231 | .sui-icon-up 232 | top 0 233 | left 1px 234 | .sui-icon-down 235 | bottom 0 236 | left 1px 237 | .sui-control-container 238 | margin 0 14px 0 10px 239 | width calc(100% - 24px) 240 | .sui-remove 241 | width $icon 242 | position absolute 243 | right 1px 244 | top 0 245 | .sui-icon-remove 246 | .sui-icon-up 247 | .sui-icon-down 248 | cursor pointer 249 | &.sui-focus 250 | > .sui-move 251 | > .sui-remove 252 | .sui-icon 253 | .sui-icon-mini 254 | opacity 0.4 255 | ~ .sui-control 256 | margin-bottom 0px 257 | 258 | .sui-map 259 | border-collapse collapse 260 | .sui-map-item 261 | > td 262 | border-bottom 1px dotted $dark-border 263 | &:first-child 264 | border-left none 265 | &:last-child 266 | > td 267 | border-bottom none 268 | .sui-icon 269 | opacity 0.1 270 | .sui-array-add 271 | .sui-icon 272 | opacity 0.2 273 | .sui-remove 274 | width $icon + 2px 275 | text-align right 276 | padding 0 1px 277 | .sui-icon-remove 278 | cursor pointer 279 | &.sui-focus 280 | > .sui-remove 281 | .sui-icon 282 | opacity 0.4 283 | 284 | .sui-disabled 285 | .sui-icon 286 | .sui-icon-mini 287 | .sui-icon:hover 288 | .sui-icon-mini:hover 289 | opacity 0.05 !important 290 | cursor default 291 | 292 | .sui-array-add 293 | text-align right 294 | .sui-icon 295 | .sui-icon-mini 296 | margin-right 1px 297 | opacity 0.2 298 | cursor pointer 299 | 300 | 301 | .sui-icon 302 | .sui-icon-mini 303 | display inline-block 304 | opacity 0.4 305 | vertical-align middle 306 | &:hover 307 | opacity 0.8 !important 308 | 309 | .sui-icon 310 | width $icon 311 | height $icon 312 | background-size $icon $icon 313 | 314 | .sui-icon-mini 315 | width $iconmini 316 | height $iconmini 317 | background-size $iconmini $iconmini 318 | 319 | .sui-folder 320 | padding 0 6px 321 | font-weight bold 322 | 323 | .sui-collapsible 324 | cursor pointer 325 | 326 | .sui-bottom-left 327 | .sui-bottom-right 328 | .sui-trigger-toggle 329 | transform rotate(180deg) 330 | 331 | .sui-choice-options > .sui-grid 332 | .sui-grid-inner 333 | width 100% 334 | 335 | .sui-choice-options 336 | > .sui-grid 337 | > .sui-grid > tbody 338 | > tr 339 | > td:first-child 340 | border-left none 341 | &:last-child 342 | > td 343 | border-bottom none 344 | 345 | .sui-grid-inner 346 | border-left 6px solid $light-background 347 | 348 | .sui-choice-header 349 | select 350 | width 100% 351 | -------------------------------------------------------------------------------- /css/sui.css: -------------------------------------------------------------------------------- 1 | .sui-control i.sui-icon-remove{background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJDSVJDTEVfX3gyRl9fTUlOVVNfMV8iIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgICAgIj4NCgk8ZyBpZD0iQ0lSQ0xFX194MkZfX01JTlVTIj4NCgkJPGc+DQoJCQk8cGF0aCBkPSJNNDUsMjlIMTljLTEuNjU3LDAtMywxLjM0My0zLDNzMS4zNDMsMywzLDNoMjZjMS42NTcsMCwzLTEuMzQzLDMtM1M0Ni42NTcsMjksNDUsMjl6IE0zMiwwQzE0LjMyNywwLDAsMTQuMzI3LDAsMzINCgkJCQlzMTQuMzI3LDMyLDMyLDMyczMyLTE0LjMyNywzMi0zMlM0OS42NzMsMCwzMiwweiBNMzIsNThDMTcuNjQxLDU4LDYsNDYuMzU5LDYsMzJDNiwxNy42NCwxNy42NDEsNiwzMiw2DQoJCQkJYzE0LjM1OSwwLDI2LDExLjY0MSwyNiwyNkM1OCw0Ni4zNTksNDYuMzU5LDU4LDMyLDU4eiIvPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPC9zdmc+DQo=")}.sui-control i.sui-icon-add{background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJDSVJDTEVfX3gyRl9fUExVU18xXyIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiPg0KCTxnIGlkPSJDSVJDTEVfX3gyRl9fUExVUyI+DQoJCTxnPg0KCQkJPHBhdGggZD0iTTQ1LDI5SDM1VjE5YzAtMS42NTctMS4zNDMtMy0zLTNzLTMsMS4zNDMtMywzdjEwSDE5Yy0xLjY1NywwLTMsMS4zNDMtMywzczEuMzQzLDMsMywzaDEwdjEwYzAsMS42NTcsMS4zNDMsMywzLDMNCgkJCQlzMy0xLjM0MywzLTNWMzVoMTBjMS42NTcsMCwzLTEuMzQzLDMtM1M0Ni42NTcsMjksNDUsMjl6IE0zMiwwQzE0LjMyNywwLDAsMTQuMzI3LDAsMzJzMTQuMzI3LDMyLDMyLDMyczMyLTE0LjMyNywzMi0zMg0KCQkJCVM0OS42NzMsMCwzMiwweiBNMzIsNThDMTcuNjQxLDU4LDYsNDYuMzU5LDYsMzJDNiwxNy42NCwxNy42NDEsNiwzMiw2YzE0LjM1OSwwLDI2LDExLjY0MSwyNiwyNkM1OCw0Ni4zNTksNDYuMzU5LDU4LDMyLDU4eiIvPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPC9zdmc+DQo=")}.sui-control i.sui-icon-up{background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJBUlJPV19feDJGX19VUF8xXyIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAgICAiPg0KCTxnIGlkPSJBUlJPV19feDJGX19VUCI+DQoJCTxnPg0KCQkJPHBhdGggZD0iTTUyLjE1OSwzOC45MThMNTIuMTU5LDM4LjkxOEwzNC4xNiwyMC45MTdsLTAuMDAxLDAuMDAxQzMzLjYxMywyMC4zNTIsMzIuODQ4LDIwLDMyLDIwYy0wLjAwMiwwLTAuMDA0LDAtMC4wMDcsMA0KCQkJCXMtMC4wMDQsMC0wLjAwNywwYy0wLjg0OCwwLTEuNjEzLDAuMzUyLTIuMTU5LDAuOTE4bC0wLjAwMS0wLjAwMWwtMTgsMThsMC4wMDgsMC4wMDhDMTEuMzE4LDM5LjQ2NCwxMSw0MC4xOTUsMTEsNDENCgkJCQljMCwxLjY1NywxLjM0MywzLDMsM2MwLjkxLDAsMS43MjUtMC40MDYsMi4yNzUtMS4wNDZsMTUuNzE4LTE1LjcxOEw0Ny45MTcsNDMuMTZsMC4wMDEtMC4wMDFDNDguNDU4LDQzLjY4LDQ5LjE5MSw0NCw1MCw0NA0KCQkJCWMxLjY1NywwLDMtMS4zNDMsMy0zQzUzLDQwLjE5MSw1Mi42OCwzOS40NTgsNTIuMTU5LDM4LjkxOHoiLz4NCgkJPC9nPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K")}.sui-control i.sui-icon-down{background-image:url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnIGlkPSJBUlJPV19feDJGX19ET1dOXzFfIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3ICAgICI+DQoJPGcgaWQ9IkFSUk9XX194MkZfX0RPV04iPg0KCQk8Zz4NCgkJCTxwYXRoIGQ9Ik01MywyM2MwLTEuNjU3LTEuMzQzLTMtMy0zYy0wLjgwOSwwLTEuNTQyLDAuMzIxLTIuMDgyLDAuODQxbC0wLjAwMS0wLjAwMUwzMS45OTMsMzYuNzY0TDE2LjI3NSwyMS4wNDYNCgkJCQlDMTUuNzI1LDIwLjQwNiwxNC45MSwyMCwxNCwyMGMtMS42NTcsMC0zLDEuMzQzLTMsM2MwLDAuODA1LDAuMzE4LDEuNTM2LDAuODM1LDIuMDc1bC0wLjAwOCwwLjAwOGwxOCwxOGwwLjAwMS0wLjAwMQ0KCQkJCUMzMC4zNzQsNDMuNjQ4LDMxLjEzOSw0NCwzMS45ODcsNDRjMC4wMDIsMCwwLjAwNCwwLDAuMDA3LDBjMC4wMDIsMCwwLjAwNCwwLDAuMDA3LDBjMC44NDksMCwxLjYxMi0wLjM1MiwyLjE1OS0wLjkxOA0KCQkJCWwwLjAwMSwwLjAwMWwxOC0xOGwtMC4wMDEtMC4wMDFDNTIuNjgsMjQuNTQzLDUzLDIzLjgwOSw1MywyM3oiLz4NCgkJPC9nPg0KCTwvZz4NCjwvZz4NCjwvc3ZnPg0K")}.sui-grid{border-collapse:collapse;}.sui-grid *{box-sizing:border-box}.sui-grid td{border-bottom:1px solid #ddd;margin:0;padding:0}.sui-grid tr:first-child td{border-top:1px solid #ddd}.sui-grid td:first-child{border-left:1px solid #ddd}.sui-grid td:last-child{border-right:1px solid #ddd}.sui-grid td.sui-top,.sui-grid td.sui-left{background-color:#fff}.sui-grid td.sui-bottom,.sui-grid td.sui-right{background-color:#f6f6f6}.sui-bottom-left,.sui-bottom-right,.sui-top-left,.sui-top-right{position:absolute;background-color:#fff}.sui-top-right{top:0;right:0;-webkit-box-shadow:-1px 1px 6px rgba(0,0,0,0.1);-moz-box-shadow:-1px 1px 6px rgba(0,0,0,0.1);box-shadow:-1px 1px 6px rgba(0,0,0,0.1);}.sui-top-right.sui-grid tr:first-child td{border-top:none}.sui-top-right.sui-grid td:last-child{border-right:none}.sui-top-left{top:0;left:0;-webkit-box-shadow:1px 1px 6px rgba(0,0,0,0.1);-moz-box-shadow:1px 1px 6px rgba(0,0,0,0.1);box-shadow:1px 1px 6px rgba(0,0,0,0.1);}.sui-top-left.sui-grid tr:first-child td{border-top:none}.sui-top-left.sui-grid td:last-child{border-left:none}.sui-bottom-right{bottom:0;right:0;-webkit-box-shadow:-1px 1px 6px rgba(0,0,0,0.1);-moz-box-shadow:-1px 1px 6px rgba(0,0,0,0.1);box-shadow:-1px 1px 6px rgba(0,0,0,0.1);}.sui-bottom-right.sui-grid tr:first-child td{border-bottom:none}.sui-bottom-right.sui-grid td:last-child{border-right:none}.sui-bottom-left{bottom:0;left:0;-webkit-box-shadow:1px 1px 6px rgba(0,0,0,0.1);-moz-box-shadow:1px 1px 6px rgba(0,0,0,0.1);box-shadow:1px 1px 6px rgba(0,0,0,0.1);}.sui-bottom-left.sui-grid tr:first-child td{border-bottom:none}.sui-bottom-left.sui-grid td:last-child{border-left:none}.sui-fill{position:absolute;width:100%;max-height:100%;top:0;left:0}.sui-append{width:100%}.sui-control,.sui-folder{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none;font-size:11px;font-family:Helvetica,"Nimbus Sans L","Liberation Sans",Arial,sans-serif;line-height:18px;vertical-align:middle;}.sui-control *,.sui-folder *{box-sizing:border-box;margin:0;padding:0}.sui-control button,.sui-folder button{line-height:18px;vertical-align:middle}.sui-control input,.sui-folder input{line-height:18px;vertical-align:middle;border:none;background-color:#f6f6f6;max-width:16em}.sui-control button:hover,.sui-folder button:hover{background-color:#fafafa;border:1px solid #ddd}.sui-control button:focus,.sui-folder button:focus{background-color:#fafafa;border:1px solid #aaa;outline:#eee solid 2px}.sui-control input:focus,.sui-folder input:focus{outline:#eee solid 2px;$outline-offset:-2px;background-color:#fafafa}.sui-control output,.sui-folder output{padding:0 6px;background-color:#fff;display:inline-block}.sui-control input[type="number"],.sui-folder input[type="number"],.sui-control input[type="date"],.sui-folder input[type="date"],.sui-control input[type="datetime-local"],.sui-folder input[type="datetime-local"],.sui-control input[type="time"],.sui-folder input[type="time"]{text-align:right}.sui-control input[type="number"],.sui-folder input[type="number"]{font-family:Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace}.sui-control input,.sui-folder input{padding:0 6px}.sui-control input[type="color"],.sui-folder input[type="color"],.sui-control input[type="checkbox"],.sui-folder input[type="checkbox"]{padding:0;margin:0}.sui-control input[type="range"],.sui-folder input[type="range"]{margin:0 8px;min-height:19px}.sui-control button,.sui-folder button{background-color:#eee;border:1px solid #aaa;border-radius:4px}.sui-control.sui-control-single input,.sui-folder.sui-control-single input,.sui-control.sui-control-single output,.sui-folder.sui-control-single output,.sui-control.sui-control-single button,.sui-folder.sui-control-single button,.sui-control.sui-control-single select,.sui-folder.sui-control-single select{width:100%}.sui-control.sui-control-single input[type="checkbox"],.sui-folder.sui-control-single input[type="checkbox"]{width:initial}.sui-control.sui-control-double input,.sui-folder.sui-control-double input,.sui-control.sui-control-double output,.sui-folder.sui-control-double output,.sui-control.sui-control-double button,.sui-folder.sui-control-double button,.sui-control.sui-control-double select,.sui-folder.sui-control-double select{width:50%}.sui-control.sui-control-double .input1,.sui-folder.sui-control-double .input1{width:calc(100% - 7em);max-width:8em}.sui-control.sui-control-double .input2,.sui-folder.sui-control-double .input2{width:7em}.sui-control.sui-control-double .input1[type="range"],.sui-folder.sui-control-double .input1[type="range"]{width:calc(100% - 7em - 16px)}.sui-control.sui-type-bool,.sui-folder.sui-type-bool{text-align:center}.sui-control.sui-invalid,.sui-folder.sui-invalid{border-left:4px solid #d00}.sui-array{list-style:none;}.sui-array .sui-array-item{border-bottom:1px dotted #aaa;position:relative;}.sui-array .sui-array-item .sui-icon,.sui-array .sui-array-item .sui-icon-mini{opacity:.1}.sui-array .sui-array-item .sui-array-add .sui-icon,.sui-array .sui-array-item .sui-array-add .sui-icon-mini{opacity:.2}.sui-array .sui-array-item > *{vertical-align:top}.sui-array .sui-array-item:first-child > .sui-move > .sui-icon-up{visibility:hidden}.sui-array .sui-array-item:last-child{border-bottom:none;}.sui-array .sui-array-item:last-child > .sui-move > .sui-icon-down{visibility:hidden}.sui-array .sui-array-item > div{display:inline-block}.sui-array .sui-array-item .sui-move{position:absolute;width:8px;height:100%;}.sui-array .sui-array-item .sui-move .sui-icon-mini{display:block;position:absolute}.sui-array .sui-array-item .sui-move .sui-icon-up{top:0;left:1px}.sui-array .sui-array-item .sui-move .sui-icon-down{bottom:0;left:1px}.sui-array .sui-array-item .sui-control-container{margin:0 14px 0 10px;width:calc(100% - 24px)}.sui-array .sui-array-item .sui-remove{width:12px;position:absolute;right:1px;top:0}.sui-array .sui-array-item .sui-icon-remove,.sui-array .sui-array-item .sui-icon-up,.sui-array .sui-array-item .sui-icon-down{cursor:pointer}.sui-array .sui-array-item.sui-focus > .sui-move .sui-icon,.sui-array .sui-array-item.sui-focus > .sui-remove .sui-icon,.sui-array .sui-array-item.sui-focus > .sui-move .sui-icon-mini,.sui-array .sui-array-item.sui-focus > .sui-remove .sui-icon-mini{opacity:.4}.sui-array ~ .sui-control{margin-bottom:0}.sui-map{border-collapse:collapse;}.sui-map .sui-map-item > td{border-bottom:1px dotted #aaa;}.sui-map .sui-map-item > td:first-child{border-left:none}.sui-map .sui-map-item:last-child > td{border-bottom:none}.sui-map .sui-map-item .sui-icon{opacity:.1}.sui-map .sui-map-item .sui-array-add .sui-icon{opacity:.2}.sui-map .sui-map-item .sui-remove{width:14px;text-align:right;padding:0 1px}.sui-map .sui-map-item .sui-icon-remove{cursor:pointer}.sui-map .sui-map-item.sui-focus > .sui-remove .sui-icon{opacity:.4}.sui-disabled .sui-icon,.sui-disabled .sui-icon-mini,.sui-disabled .sui-icon:hover,.sui-disabled .sui-icon-mini:hover{opacity:.05 !important;cursor:default}.sui-array-add{text-align:right;}.sui-array-add .sui-icon,.sui-array-add .sui-icon-mini{margin-right:1px;opacity:.2;cursor:pointer}.sui-icon,.sui-icon-mini{display:inline-block;opacity:.4;vertical-align:middle;}.sui-icon:hover,.sui-icon-mini:hover{opacity:.8 !important}.sui-icon{width:12px;height:12px;background-size:12px 12px}.sui-icon-mini{width:8px;height:8px;background-size:8px 8px}.sui-folder{padding:0 6px;font-weight:bold}.sui-collapsible{cursor:pointer}.sui-bottom-left .sui-trigger-toggle,.sui-bottom-right .sui-trigger-toggle{transform:rotate(180deg)}.sui-choice-options > .sui-grid,.sui-grid-inner{width:100%}.sui-choice-options > .sui-grid > tr > td:first-child,.sui-choice-options > .sui-grid > tbody > tr > td:first-child{border-left:none}.sui-choice-options > .sui-grid > tr:last-child > td,.sui-choice-options > .sui-grid > tbody > tr:last-child > td{border-bottom:none}.sui-grid-inner{border-left:6px solid #f6f6f6}.sui-choice-header select{width:100%} -------------------------------------------------------------------------------- /src/sui/Sui.hx: -------------------------------------------------------------------------------- 1 | package sui; 2 | 3 | #if !macro 4 | import js.Browser; 5 | import js.html.DOMElement as Element; 6 | import sui.components.Grid; 7 | import sui.controls.*; 8 | import sui.controls.Options; 9 | using thx.Arrays; 10 | using thx.Functions; 11 | using thx.Nulls; 12 | using thx.stream.dom.Dom; 13 | using thx.stream.Emitter; 14 | using dots.Query; 15 | using dots.Html; 16 | #else 17 | import haxe.macro.Context; 18 | import haxe.macro.Expr; 19 | import haxe.macro.ExprTools; 20 | using thx.Strings; 21 | #end 22 | 23 | #if (haxe_ver < "3.2") 24 | import Map.Map; 25 | #end 26 | 27 | class Sui { 28 | #if !macro 29 | public var el(default, null) : Element; 30 | var grid : Grid; 31 | public function new() { 32 | grid = new Grid(); 33 | el = grid.el; 34 | } 35 | 36 | public function array(?label : String, ?defaultValue : Array, ?defaultElementValue : T, createControl : T -> IControl, ?options : Options, callback : Array -> Void) 37 | return control(label, createArray(defaultValue, defaultElementValue, createControl, options), callback); 38 | 39 | public function bool(?label : String, ?defaultValue = false, ?options : Options, callback : Bool -> Void) 40 | return control(label, createBool(defaultValue, options), callback); 41 | 42 | // public function choice(?label : String, ?defaultValue : String, createControl : String -> WithElement, list : Array<{ value : String, label : String }>) 43 | // return control(label, createChoice(defaultValue, createControl, list), callback); 44 | 45 | public function color(?label : String, ?defaultValue = "#AA0000", ?options : OptionsColor, callback : String -> Void) 46 | return control(label, createColor(defaultValue, options), callback); 47 | 48 | public function date(?label : String, ?defaultValue : Date, ?options : OptionsKindDate, callback : Date -> Void) 49 | return control(label, createDate(defaultValue, options), callback); 50 | 51 | // public function enumMap(?label : String, ?defaultValue : Map, createKeyControl : TKey -> IControl, createValueControl : TValue -> IControl, ?options : Options, callback : Map -> Void) 52 | // return control(label, createEnumMap(defaultValue, createKeyControl, createValueControl, options), callback); 53 | 54 | public function float(?label : String, ?defaultValue = 0.0, ?options : OptionsKindFloat, callback : Float -> Void) 55 | return control(label, createFloat(defaultValue, options), callback); 56 | 57 | public function folder(label : String, ?options : OptionsFolder) { 58 | var collapsible = (options.collapsible).or(true), 59 | collapsed = (options.collapsed).or(false), 60 | sui = new Sui(), 61 | header = { 62 | el : dots.Html.parse('
    63 | 64 | $label
    ') 65 | }, 66 | trigger = Query.first('.sui-trigger-toggle', header.el); 67 | 68 | if(collapsible) { 69 | header.el.classList.add('sui-collapsible'); 70 | 71 | if(collapsed) { 72 | sui.grid.el.style.display = "none"; 73 | } 74 | 75 | var collapse = header.el.streamClick() 76 | .map(function(_) return collapsed = !collapsed) 77 | .negate(); 78 | 79 | collapse.subscribe( 80 | sui.grid.el.subscribeToggleVisibility() 81 | .join(trigger.subscribeSwapClass('sui-icon-collapse', 'sui-icon-expand')) 82 | ); 83 | } else { 84 | trigger.style.display = "none"; 85 | } 86 | 87 | sui.grid.el.classList.add("sui-grid-inner"); 88 | grid.add(VerticalPair(header, sui.grid)); 89 | return sui; 90 | } 91 | 92 | public function int(?label : String, ?defaultValue = 0, ?options : OptionsKindInt, callback : Int -> Void) 93 | return control(label, createInt(defaultValue, options), callback); 94 | 95 | public function intMap(?label : String, ?defaultValue : Map, createValueControl : T -> IControl, ?options : Options, callback : Map -> Void) 96 | return control(label, createIntMap(defaultValue, function(v) return createInt(v), createValueControl, options), callback); 97 | 98 | public function label(?defaultValue = "", ?label : String, ?callback : String -> Void) 99 | return control(label, createLabel(defaultValue), callback); 100 | 101 | public function objectMap(?label : String, ?defaultValue : Map, createKeyControl : TKey -> IControl, createValueControl : TValue -> IControl, ?options : Options, callback : Map -> Void) 102 | return control(label, createObjectMap(defaultValue, createKeyControl, createValueControl, options), callback); 103 | 104 | public function stringMap(?label : String, ?defaultValue : Map, createValueControl : T -> IControl, ?options : Options, callback : Map -> Void) 105 | return control(label, createStringMap(defaultValue, function(v) return createText(v), createValueControl, options), callback); 106 | 107 | public function text(?label : String, ?defaultValue = "", ?options : OptionsKindText, callback : String -> Void) 108 | return control(label, createText(defaultValue, options), callback); 109 | 110 | public function trigger(actionLabel : String, ?label : String, ?options : Options, callback : Void -> Void) 111 | return control(label, new TriggerControl(actionLabel, options), function(_) callback()); 112 | 113 | // statics 114 | static public function createArray(?defaultValue : Array, ?defaultElementValue : T, createControl : T -> IControl, ?options : Options) 115 | return new ArrayControl((defaultValue).or([]), defaultElementValue, createControl, options); 116 | 117 | static public function createBool(?defaultValue = false, ?options : Options) 118 | return new BoolControl(defaultValue, options); 119 | /* 120 | public function createChoice(?defaultValue : String, createControl : String -> WithElement, list : Array<{ value : String, label : String }>) { 121 | var select = createText((defaultValue).or(list[0].value), { 122 | listonly : true, 123 | list : list 124 | }), 125 | el = Html.parse('
    126 |
    127 |
    128 |
    '), 129 | header = Query.first(".sui-choice-header", el), 130 | options = Query.first(".sui-choice-options", el); 131 | header.appendChild(select.el); 132 | select.streams.value.subscribe(function(value) { 133 | var container = createControl(value); 134 | options.innerHTML = ""; 135 | container.with(options.appendChild(container.el)); 136 | }); 137 | return new MultiControl(); 138 | } 139 | */ 140 | static public function createColor(?defaultValue = "#AA0000", ?options : OptionsColor) 141 | return new ColorControl(defaultValue, options); 142 | 143 | static public function createDate(?defaultValue : Date, ?options : OptionsKindDate) { 144 | if(null == defaultValue) 145 | defaultValue = Date.now(); 146 | return switch [(options.listonly).or(false), (options.kind).or(DateOnly)] { 147 | case [true, _]: 148 | new DateSelectControl(defaultValue, options); 149 | case [_, DateTime]: 150 | new DateTimeControl(defaultValue, options); 151 | case _: 152 | new DateControl(defaultValue, options); 153 | }; 154 | } 155 | 156 | static public function collapsible(?label : String, ?collapsed = false, ?attachTo : Element, ?position : Anchor) { 157 | var sui = new Sui(), 158 | folder = sui.folder(label.or(""), { collapsible : true, collapsed : collapsed }); 159 | sui.attach(attachTo, position); 160 | return folder; 161 | } 162 | 163 | // static public function createEnumMap(?defaultValue : Map, createKeyControl : TKey -> IControl, createValueControl : TValue -> IControl, ?options : Options) 164 | // return new MapControl(cast defaultValue, function() return cast new haxe.ds.EnumValueMap(), createKeyControl, createValueControl, options); 165 | 166 | static public function createFloat(?defaultValue = 0.0, ?options : OptionsKindFloat) 167 | return switch [(options.listonly).or(false), (options.kind).or(FloatNumber)] { 168 | case [true, _]: 169 | new NumberSelectControl(defaultValue, options); 170 | case [_, FloatTime]: 171 | new TimeControl(defaultValue, options); 172 | case [_, _]: 173 | (null != options && options.min != null && options.max != null) ? 174 | new FloatRangeControl(defaultValue, options) : 175 | new FloatControl(defaultValue, options); 176 | }; 177 | 178 | static public function createInt(?defaultValue = 0, ?options : OptionsKindInt) 179 | return (options.listonly).or(false) ? 180 | new NumberSelectControl(defaultValue, options) : 181 | (null != options && options.min != null && options.max != null) ? 182 | new IntRangeControl(defaultValue, options) : 183 | new IntControl(defaultValue, options); 184 | 185 | static public function createIntMap(?defaultValue : Map, createKeyControl : Int -> IControl, createValueControl : TValue -> IControl, ?options : Options) 186 | return new MapControl(defaultValue, function() return new haxe.ds.IntMap(), createKeyControl, createValueControl, options); 187 | 188 | static public function createLabel(?defaultValue = "", ?label : String, ?callback : String -> Void) 189 | return new LabelControl(defaultValue); 190 | 191 | static public function createObjectMap(?defaultValue : Map, createKeyControl : TKey -> IControl, createValueControl : TValue -> IControl, ?options : Options) 192 | return new MapControl(defaultValue, function() return new haxe.ds.ObjectMap(), createKeyControl, createValueControl, options); 193 | 194 | static public function createStringMap(?defaultValue : Map, createKeyControl : String -> IControl, createValueControl : TValue -> IControl, ?options : Options) 195 | return new MapControl(defaultValue, function() return new haxe.ds.StringMap(), createKeyControl, createValueControl, options); 196 | 197 | static public function createText(?defaultValue = "", ?options : OptionsKindText) 198 | return switch [(options.listonly).or(false), (options.kind).or(PlainText)] { 199 | case [true, _]: new TextSelectControl(defaultValue, options); 200 | case [_, TextEmail]: new EmailControl(defaultValue, options); 201 | case [_, TextPassword]: new PasswordControl(defaultValue, options); 202 | case [_, TextTel]: new TelControl(defaultValue, options); 203 | case [_, TextSearch]: new SearchControl(defaultValue, options); 204 | case [_, TextUrl]: new UrlControl(defaultValue, options); 205 | case [_, _]: new TextControl(defaultValue, options); 206 | }; 207 | 208 | static public function createTrigger(actionLabel : String, ?options : Options) 209 | return new TriggerControl(actionLabel, options); 210 | 211 | // generic binding 212 | public function control>(?label : String, control : TControl, callback : T -> Void) : TControl { 213 | grid.add(null == label ? Single(control) : HorizontalPair(new LabelControl(label), control)); 214 | control.streams.value.subscribe(callback); 215 | return control; 216 | } 217 | 218 | public function attach(?el : Element, ?anchor : Anchor) { 219 | if(null == el) { 220 | el = Browser.document.body; 221 | } 222 | this.el.classList.add((anchor).or(el == Browser.document.body ? Anchor.topRight : Anchor.append)); 223 | el.appendChild(this.el); 224 | } 225 | 226 | static function __init__() { 227 | #if (sui_embed_css == 1) 228 | dots.Dom.addCss(sui.macro.Embed.file("css/sui.css")); 229 | #end 230 | } 231 | #end 232 | 233 | // label (readonly?) 234 | macro public function bind(sui : ExprOf, variable : Expr, ?options : Expr) { 235 | //trace(sui); 236 | var id = switch variable.expr { 237 | case EField(e, field): 238 | (ExprTools.toString(e) + "." + field).split(".").slice(1).join("."); 239 | case EConst(CIdent(id)): 240 | id; 241 | case _: 242 | Context.error('invalid expression $variable', variable.pos); 243 | }, 244 | type = Context.typeof(variable); 245 | id = id.humanize(); 246 | 247 | return switch type { 248 | case TInst(_.toString() => "String", _): 249 | macro $e{sui}.text($v{id}, $e{variable}, $e{options}, function(v) $e{variable} = v); 250 | case TInst(_.toString() => "Date", _): 251 | macro $e{sui}.date($v{id}, $e{variable}, $e{options}, function(v) $e{variable} = v); 252 | case TInst(_.toString() => "Array", t): 253 | var f = bindType(t[0]); 254 | macro $e{sui}.array($v{id}, $e{variable}, null, 255 | function(v) return $e{f}(v), 256 | function(v) $e{variable} = v); 257 | case TInst(cls, params): 258 | var fields : Array = []; 259 | cls.get().fields.get().map(function(field) { 260 | if(!field.isPublic) return; 261 | var name = field.name, 262 | label = name.humanize(); 263 | switch field.kind { 264 | case FVar(_, _): 265 | var createControl = bindType(field.type), 266 | T = haxe.macro.TypeTools.toComplexType(field.type); 267 | var expr = macro folder.control($v{label}, $e{createControl}(o.$name), function(v : $T) o.$name = v); 268 | fields.push(expr); 269 | case FMethod(_): 270 | var arity = thx.macro.MacroTypes.getArity(Context.follow(field.type)); 271 | if(arity != 0) return; 272 | var expr = macro folder.control(Sui.createTrigger($v{label}), function(_) o.$name()); 273 | fields.push(expr); 274 | } 275 | }); 276 | macro { 277 | var o = $e{variable}, 278 | folder = $e{sui}.folder($v{id}); 279 | $b{fields}; 280 | folder; 281 | }; 282 | case TAbstract(_.toString() => "Bool", _): 283 | macro $e{sui}.bool($v{id}, $e{variable}, $e{options}, function(v) $e{variable} = v); 284 | case TAbstract(_.toString() => "Float", _): 285 | macro $e{sui}.float($v{id}, $e{variable}, $e{options}, function(v) $e{variable} = v); 286 | case TAbstract(_.toString() => "Int", _): 287 | macro $e{sui}.int($v{id}, $e{variable}, $e{options}, function(v) $e{variable} = v); 288 | case TAbstract(_.toString() => "Map", args): 289 | var createValueControl = bindType(args[1]); 290 | switch args[0] { 291 | case TInst(_.toString() => "Int", _): 292 | macro $e{sui}.intMap( 293 | $v{id}, 294 | $e{variable}, 295 | function(v) return $e{createValueControl}(v), 296 | function(v) $e{variable} = cast v); 297 | case TInst(_.toString() => "String", _): 298 | macro $e{sui}.stringMap( 299 | $v{id}, 300 | $e{variable}, 301 | function(v) return $e{createValueControl}(v), 302 | function(v) $e{variable} = cast v); 303 | // TODO enum, object 304 | case _: 305 | Context.error('unsupported map/key parameter ${args[0]}', variable.pos); 306 | } 307 | case TAbstract(_.toString() => t, e): 308 | Context.error('unsupported abstract $t, $e', variable.pos); 309 | case TEnum(t, params): 310 | var T = haxe.macro.TypeTools.toComplexType(type); 311 | macro function(v : $T) { 312 | return new MultiControl(); 313 | }; 314 | case TFun([],TAbstract(_.toString() => "Void",[])): 315 | macro $e{sui}.trigger($v{id}, $e{variable}, $e{options}); 316 | case _: 317 | Context.error('unsupported type $type', variable.pos); 318 | }; 319 | } 320 | #if macro 321 | public static function bindType(type : haxe.macro.Type) : Expr { 322 | return switch type { 323 | case TInst(_.toString() => "String", _): 324 | macro Sui.createText; 325 | case TInst(_.toString() => "Date", _): 326 | macro Sui.createDate; 327 | case TInst(_.toString() => "Array", t): 328 | var f = bindType(t[0]); 329 | macro function(v) return Sui.createArray(v, null, function(v) return $e{f}(v), null); 330 | case TInst(cls, params): 331 | var fields : Array = []; 332 | cls.get().fields.get().map(function(field) { 333 | if(!field.isPublic) return; 334 | var name = field.name, 335 | label = name.humanize(); 336 | switch field.kind { 337 | case FVar(_, _): 338 | var createControl = bindType(field.type), 339 | T = haxe.macro.TypeTools.toComplexType(field.type); 340 | // TODO remove cast 341 | var expr = macro sui.control($v{label}, $e{createControl}(o.$name), function(v : $T) o.$name = v); 342 | fields.push(expr); 343 | case FMethod(_): 344 | var arity = thx.macro.MacroTypes.getArity(Context.follow(field.type)); 345 | if(arity != 0) return; 346 | var expr = macro sui.control(Sui.createTrigger($v{label}), function(_) o.$name()); 347 | fields.push(expr); 348 | } 349 | }); 350 | macro function(o) { 351 | var sui = new Sui(); 352 | $b{fields}; 353 | return sui; 354 | }; 355 | case TAbstract(_.toString() => "Bool", _): 356 | macro Sui.createBool; 357 | case TAbstract(_.toString() => "Float", _): 358 | macro Sui.createFloat; 359 | case TAbstract(_.toString() => "Int", _): 360 | macro Sui.createInt; 361 | case TAbstract(_.toString() => "Map", args): 362 | var createKeyControl = bindType(args[0]), 363 | createValueControl = bindType(args[1]); 364 | switch args[0] { 365 | case TInst(_.toString() => "Int", _): 366 | macro function(v) return Sui.createIntMap( 367 | v, 368 | function(v) return $e{createKeyControl}(v), 369 | function(v) return $e{createValueControl}(v) 370 | ); 371 | case TInst(_.toString() => "String", _): 372 | macro function(v) return Sui.createStringMap( 373 | v, 374 | function(v) return $e{createKeyControl}(v), 375 | function(v) return $e{createValueControl}(v) 376 | ); 377 | // TODO enum, object 378 | case _: 379 | Context.error('unsupported map/key parameter ${args[0]}', Context.currentPos()); 380 | } 381 | case TAbstract(_.toString() => t, e): 382 | Context.error('unsupported abstract $t, $e', Context.currentPos()); 383 | case TEnum(t, params): 384 | var T = haxe.macro.TypeTools.toComplexType(type); 385 | macro function(v : $T) { 386 | var controls = []; 387 | return new sui.controls.MultiControl(v, null, controls); 388 | }; 389 | case TFun([],TAbstract(_.toString() => "Void",[])): 390 | macro Sui.createTrigger; 391 | case _: 392 | Context.error('unsupported type $type', Context.currentPos()); 393 | }; 394 | } 395 | #end 396 | } 397 | 398 | @:enum abstract Anchor(String) to String { 399 | public var topLeft = "sui-top-left"; 400 | public var topRight = "sui-top-right"; 401 | public var bottomLeft = "sui-bottom-left"; 402 | public var bottomRight = "sui-bottom-right"; 403 | public var fill = "sui-fill"; 404 | public var append = "sui-append"; 405 | } 406 | --------------------------------------------------------------------------------