@wrnrlr/prelude 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/deno.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrnrlr/prelude",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "exports": "./src/mod.ts",
5
5
  "compilerOptions": {
6
6
  "strict": false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wrnrlr/prelude",
3
3
  "type": "module",
4
- "version": "0.1.7",
4
+ "version": "0.1.8",
5
5
  "author": "Werner Laurensse",
6
6
  "description": "A signal based frontend library with fine-grained reactivity",
7
7
  "main": "./src/mod.ts",
package/readme.md CHANGED
@@ -181,6 +181,27 @@ h(Router,[
181
181
  ])
182
182
  ```
183
183
 
184
+ ## Developer Information
185
+
186
+ Only Deno is required to run the development server.
187
+
188
+ Install Dependencies
189
+ ```sh
190
+ deno install
191
+ ```
192
+
193
+ Start Development server
194
+
195
+ ```sh
196
+ deno task dev
197
+ ```
198
+
199
+ Run tests
200
+
201
+ ```sh
202
+ deno task test
203
+ ```
204
+
184
205
  ## Learn More
185
206
 
186
207
  * [API Reference]()
package/src/mod.ts CHANGED
@@ -12,7 +12,7 @@ export {resource,makeAbortable,abortable} from './resource.js'
12
12
  // export {Input,Table} from './components.js'
13
13
  // export * from './canvas.js'
14
14
 
15
- const r:Runtime|undefined = /*#__PURE__*/ (typeof window === 'object') ? runtime(window as any) : undefined
15
+ const r:Runtime = /*#__PURE__*/ (typeof window === 'object') ? runtime(window as any) : undefined as any
16
16
 
17
17
  /** h
18
18
  @example Element with a single child
@@ -29,7 +29,7 @@ h(Input,{onInput:e => {}})
29
29
  ```
30
30
  @group Hyperscript
31
31
  */
32
- const h:HyperScript|undefined = /*#__PURE__*/ r ? hyperscript(r) : undefined
32
+ const h:HyperScript = /*#__PURE__*/ r ? hyperscript(r) : undefined
33
33
 
34
34
  const render = /*#__PURE__*/ r?.render
35
35
 
@@ -0,0 +1,8 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type AccordionProps = {
4
+ }
5
+
6
+ export function Accordion(props:AccordionProps):undefined|any {
7
+ return h('.accordion', {},'')
8
+ }
@@ -0,0 +1,9 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type ButtonProps = {
4
+ children?: any[]
5
+ }
6
+
7
+ export function Button(props:AccordionProps) {
8
+ return h('button', {type: 'button'}, props.children)
9
+ }
@@ -0,0 +1,8 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type CanvasProps = {
4
+ }
5
+
6
+ export function Canvas(props:CanvasProps) {
7
+ return h('canvas')
8
+ }
package/src/ui/date.ts ADDED
@@ -0,0 +1,8 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type DateInputProps = {
4
+ }
5
+
6
+ export function DateInput(props:DateInputProps) {
7
+ return h('input', {type: 'date'})
8
+ }
@@ -0,0 +1,12 @@
1
+ import { effect } from '../reactive.ts'
2
+ import { h } from './h.ts'
3
+
4
+ export function Dialog(props) {
5
+ return h('dialog', {
6
+ ref(r) {
7
+ r.addEventListener('cancel', ()=>props.show(false))
8
+ const show = props.modal ? ()=>r.showModal() : ()=>r.show()
9
+ effect(() => props.show() ? show() : r.close())
10
+ },
11
+ }, props.children)
12
+ }
@@ -0,0 +1,8 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type FilterProps = {
4
+ }
5
+
6
+ export function Filter(props:FilterProps) {
7
+ return h('.filter')
8
+ }
package/src/ui/form.ts ADDED
@@ -0,0 +1,7 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type FormProps = {}
4
+
5
+ export function Form(props:FormProps) {
6
+ return h('form', props)
7
+ }
package/src/ui/h.ts ADDED
@@ -0,0 +1,48 @@
1
+ import {runtime,type Runtime} from '../runtime.ts'
2
+ import {hyperscript,type HyperScript} from '../hyperscript.ts'
3
+
4
+ let _fontMetricsCtx:undefined|CanvasRenderingContext2D
5
+
6
+ export function fontMetrics(font:string, text:string) {
7
+ if (!_fontMetricsCtx) _fontMetricsCtx = document.createElement('canvas').getContext('2d')
8
+ _fontMetricsCtx!.font = font;
9
+ return _fontMetricsCtx!.measureText(text)
10
+ }
11
+
12
+ export function renderEffect(fn:any):any {
13
+ return requestAnimationFrame(()=>effect(fn))
14
+ }
15
+
16
+ const r:Runtime|undefined = /*#__PURE__*/ (typeof window === 'object') ? runtime(window as any) : undefined
17
+
18
+ /** h
19
+ @example Element with a single child
20
+ ```js
21
+ h('h1','Hello World!')
22
+ ```
23
+ @example Element with multiple children
24
+ ```js
25
+ h('p',['Hello ',h('em','World!')])
26
+ ```
27
+ @example Component with event handler
28
+ ```js
29
+ h(Input,{onInput:e => {}})
30
+ ```
31
+ @group Hyperscript
32
+ */
33
+ const h:HyperScript|undefined = /*#__PURE__*/ r ? hyperscript(r) : undefined
34
+
35
+ const render = /*#__PURE__*/ r?.render
36
+
37
+ // import {signal,wrap} from './reactive.ts'
38
+
39
+ // /**
40
+ // @group Utils
41
+ // */
42
+ // export function $(a:any,b:any):any {
43
+ // const t = typeof a
44
+ // if (t==='function') return wrap(a,b)
45
+ // else return signal(a,b)
46
+ // }
47
+
48
+ export {h,render}
@@ -0,0 +1,5 @@
1
+ import {h} from './h.ts'
2
+
3
+ export function Img(props:ImgProps) {
4
+ return h('img', props)
5
+ }
@@ -0,0 +1,49 @@
1
+ import {h,fontMetrics,renderEffect} from './h.ts'
2
+ import {type Getter, type Setter} from '../reactive.ts'
3
+
4
+ export type InputProps = {
5
+ ref?: (r:HTMLElement)=>void,
6
+ id?: string,
7
+ autocomplete?: 'on'|'off',
8
+ autosize?: (r:HTMLElement, props:any)=>(r:HTMLElement)=>void,
9
+ class?: string,
10
+ disabled?: boolean,
11
+ name?: string,
12
+ placeholder?: string,
13
+ onInput?: (e:Event) => void,
14
+ value: (Getter<string>&Setter<string>)|(Getter<number>&Setter<number>),
15
+ style?: string,
16
+ type?: string,
17
+ }
18
+
19
+ export function Input(props: InputProps): any {
20
+ props.onInput = props.onInput || ((e:Event) => props.value(e?.target?.value))
21
+ props.autocomplete = props.autocomplete || 'off'
22
+ if (props.autosize) props.ref = (r)=>props.autosize(r,props)
23
+ return h('input', props)
24
+ }
25
+
26
+ const autosize = (r:HTMLElement, props:any) => {
27
+ const style = window.getComputedStyle(r)
28
+ renderEffect(() => {
29
+ const font = style.getPropertyValue('font')
30
+ const metrics = fontMetrics(font, props.value().toString())
31
+ r.style.width = metrics.width + 'px'
32
+ })
33
+ }
34
+
35
+ Input.prototype.autosize = autosize
36
+
37
+ // return h('input', {
38
+ // ref,
39
+ // id: props.id,
40
+ // autocomplete: props.autocomplete || 'off',
41
+ // class: props.class,
42
+ // disabled: props.disabled,
43
+ // onInput: (e:Event) => props.value(e?.target?.value),
44
+ // name: props.name,
45
+ // placeholder: props.placeholder,
46
+ // type: props.type,
47
+ // style: props.style,
48
+ // value: props.value,
49
+ // })
package/src/ui/mod.ts ADDED
@@ -0,0 +1,12 @@
1
+ export {Accordion, type AccordionProps} from './accordion.ts'
2
+ export {Button, type ButtonProps} from './button.ts'
3
+ export {Canvas} from './canvas.ts'
4
+ export {DateInput} from './date.ts'
5
+ export {Input, type InputProps} from './input.ts'
6
+ export {Form} from './form.ts'
7
+ export {Tab} from './tab.ts'
8
+ export {Filter, type FilterProps} from './filter.ts'
9
+ export {Multiselect, type MultiselectProps} from './multiselect.ts'
10
+ export {Select, type SelectProps} from './select.ts'
11
+ export {Table, type TableProps} from './table.ts'
12
+ export {Upload} from './upload.ts'
@@ -0,0 +1,42 @@
1
+ import {h} from './h.ts'
2
+ import {signal} from "../reactive.ts"
3
+ import {Dialog} from './dialog.ts'
4
+ import {Button} from './button.ts'
5
+
6
+ export type MultiselectProps = {
7
+ key: string | ((item:unknown)=>string),
8
+ label: string | ((item:unknown)=>string),
9
+ options: string[] | (()=>string[]),
10
+ disabled: boolean | (()=>boolean),
11
+ value: string[] | ((value:string[])=>void),
12
+ }
13
+
14
+ export function Multiselect(props:MultiselectProps) {
15
+ const showDialog = signal(true)
16
+ const key = props.key || (f => f)
17
+ const label = props.label || (f => f)
18
+ const add = item => props.value([...props.value(), key(item)])
19
+ const remove = i => _ => props.value(items=>items.toSpliced(i,1))
20
+ const options = () => (props.options.call ? props.options() : props.options).filter(e=>!props.value().includes(e))
21
+ const value = (...args:any[]) => (!args.length) ? props.value() : (show(false), add(key(args[0])))
22
+ const disabled = () => ((props?.disabled?.call ? props.disabled() : props.disabled))
23
+
24
+ return h('span.multiselect', [
25
+ h(List, {each:()=>value}, (e,i) => h('span', [
26
+ h(Button, {disabled}, ()=>label(e())),
27
+ h(Button, {disabled, onClick:remove(i())})
28
+ ])),
29
+ h('span', {style:'position:relative'}, [
30
+ h(Button, {
31
+ onClick: _ => showDialog(b=>!b),
32
+ class: () => showDialog() ? 'minus' : 'add',
33
+ disabled: () => disabled() || !options().length,
34
+ }, '+'),
35
+ h(Dialog, {show:()=>showDialog}, h(Options, {
36
+ options: () => options,
37
+ value: () => value,
38
+ label
39
+ }))
40
+ ])
41
+ ])
42
+ }
@@ -0,0 +1 @@
1
+ import {h} from './h.ts'
@@ -0,0 +1,28 @@
1
+ import {signal} from '../reactive.ts'
2
+ import { h } from './h.ts'
3
+ import {Dialog} from './dialog.ts'
4
+ import {Button} from './button.ts'
5
+
6
+ export type SelectProps = {
7
+ label?: (value:any)=>string,
8
+ key?: (value:any)=>string,
9
+ options: any[] | (()=>any[]),
10
+ }
11
+
12
+ export function Select(props:SelectProps) {
13
+ const show = signal(false)
14
+ const label = props.label || (f => f)
15
+ const key = props.key || (f => f)
16
+ const placeholder = h('span.placeholder', props.placeholder)
17
+ const display = () => props?.value?.() ? label(props.value()) : placeholder
18
+ const value = (...args) => (!args.length) ? props.value() : (show(false), props.value(key(args[0])))
19
+
20
+ return h('span.select', {id: props.id, class:props.class, style:'position:relative'}, [
21
+ h(Button, {onClick:_ => show(b=>!b)}, display),
22
+ h(Dialog, {show:()=>show}, h(Options, {
23
+ options: props.options,
24
+ value: ()=>value,
25
+ label
26
+ }))
27
+ ])
28
+ }
package/src/ui/tab.ts ADDED
@@ -0,0 +1,7 @@
1
+ export type TabProps = {
2
+
3
+ }
4
+
5
+ export function Tab(props: TabProps) {
6
+
7
+ }
@@ -0,0 +1,9 @@
1
+ export type TableProps = {
2
+ }
3
+
4
+ export function Table(props:TableProps) {
5
+ return h('table',[
6
+ h('thead',[]),
7
+ h('tbody',[])
8
+ ])
9
+ }
@@ -0,0 +1,7 @@
1
+ import {h} from './h.ts'
2
+
3
+ export type UploadProps = {}
4
+
5
+ export function Upload(props:UploadProps) {
6
+ return h('input', props)
7
+ }
@@ -18,6 +18,7 @@ const playing = signal(true)
18
18
  const examples = [
19
19
  {name:'counter',title:'Counter'},
20
20
  {name:'todo',title:'Todo App'},
21
+ {name:'select',title:'Select'},
21
22
  // {name:'admin',title:'Admin'}
22
23
  ]
23
24
 
@@ -98,7 +99,7 @@ function SrcMenu(props) {
98
99
  function Playground() {
99
100
  const show = signal(true)
100
101
  const src = signal('example')
101
- const file = signal('counter')
102
+ const file = signal('select')
102
103
  const local = signal(examples)
103
104
  const content = signal('')
104
105
  const exs = ()=>examples
@@ -0,0 +1,27 @@
1
+ <!DOCTYPE html>
2
+ <title>Select</title>
3
+
4
+ <script type="module">
5
+
6
+ import {h,signal,render,memo,List} from 'https://esm.sh/@wrnrlr/prelude'
7
+
8
+ function Select(props) {
9
+ const state = memo(() => ({
10
+ options: props.options(),
11
+ index: signal(-1)
12
+ }))
13
+ return [
14
+ ()=>state().index,
15
+ h(List, {each:()=>state().options}, (o,i) =>
16
+ h('button', {onClick:e=>{state().index(i())}}, o))
17
+ ]
18
+ }
19
+
20
+ function App() {
21
+ const options = signal(['apple', 'banana', 'cherry'])
22
+ return h(Select, {options:()=>options})
23
+ }
24
+
25
+ render(App, document.body)
26
+
27
+ </script>
package/www/ui.html ADDED
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <title>Demo</title>
3
+
4
+ <script src="./accordion.ts"></script>
5
+ <script src="./button.ts"></script>
6
+ <script src="./canvas.ts"></script>
7
+ <script src="./date.ts"></script>
8
+ <script src="./editable.ts"></script>
9
+ <script src="./filter.ts"></script>
10
+ <script src="./multiselect.ts"></script>
11
+ <script src="./select.ts"></script>
12
+ <script src="./table.ts"></script>
13
+
14
+ <body>
15
+ <h1>UI</h1>
16
+ <div id="example"></div>
17
+ </body>
18
+
19
+ <script type="module">
20
+ function App() {}
21
+
22
+ function AccordionExample() {
23
+ return h('#accordion', h(Accordion))
24
+ }
25
+ function ButtonExample() {
26
+ return h('#button', h(Button))
27
+ }
28
+ function CanvasExample() {
29
+ return h('#canvas', h(Canvas))
30
+ }
31
+ function DateInputExample() {
32
+ return h('#date', h(DateInput))
33
+ }
34
+ function EditableExample() {
35
+ return h('#editable', h(Editbale))
36
+ }
37
+ function FilterExample() {
38
+ return h('#filter', h(Filter))
39
+ }
40
+ function MultiselectExample() {
41
+ return h('#multiselect', h(Multiselect))
42
+ }
43
+ function SelectExample() {
44
+ return h('#select', h(Select))
45
+ }
46
+ function TableExample() {
47
+ return h('#table', h(Table))
48
+ }
49
+ </script>