amateras 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +39 -38
  2. package/build/core.js +1 -1
  3. package/build/css-keyframes.js +1 -0
  4. package/build/css-property.js +1 -0
  5. package/build/css-variable.js +1 -0
  6. package/build/for.js +1 -1
  7. package/build/i18n.js +1 -1
  8. package/build/if.js +1 -1
  9. package/build/import-map.js +1 -1
  10. package/build/match.js +1 -1
  11. package/build/meta.js +1 -1
  12. package/build/prefetch.js +1 -1
  13. package/build/router.js +1 -1
  14. package/build/signal.js +1 -1
  15. package/build/store.js +1 -1
  16. package/build/ui.js +1 -1
  17. package/build/utils.js +1 -1
  18. package/build/widget.js +1 -1
  19. package/package.json +3 -2
  20. package/packages/core/src/index.ts +86 -31
  21. package/packages/core/src/lib/hmr.ts +4 -4
  22. package/packages/core/src/structure/ElementProto.ts +33 -11
  23. package/packages/core/src/structure/GlobalState.ts +13 -4
  24. package/packages/core/src/structure/NodeProto.ts +2 -4
  25. package/packages/core/src/structure/Proto.ts +37 -23
  26. package/packages/core/src/structure/TextProto.ts +1 -2
  27. package/packages/css/README.md +18 -15
  28. package/packages/css/src/ext/property.ts +2 -3
  29. package/packages/css/src/index.ts +1 -1
  30. package/packages/css/src/structure/$CSSProperty.ts +4 -0
  31. package/packages/css/src/structure/$CSSVariable.ts +1 -1
  32. package/packages/css/src/types.ts +5 -0
  33. package/packages/for/src/global.ts +12 -3
  34. package/packages/for/src/structure/For.ts +5 -3
  35. package/packages/i18n/README.md +16 -24
  36. package/packages/i18n/src/index.ts +26 -5
  37. package/packages/i18n/src/structure/I18n.ts +2 -4
  38. package/packages/i18n/src/structure/I18nSession.ts +4 -2
  39. package/packages/i18n/src/structure/I18nTranslation.ts +15 -26
  40. package/packages/idb/src/structure/$IDBStore.ts +2 -2
  41. package/packages/if/src/global.ts +15 -4
  42. package/packages/if/src/index.ts +18 -8
  43. package/packages/if/src/structure/Condition.ts +16 -13
  44. package/packages/if/src/structure/ConditionStatement.ts +9 -9
  45. package/packages/match/src/global.ts +9 -3
  46. package/packages/match/src/structure/Match.ts +1 -1
  47. package/packages/meta/src/index.ts +4 -5
  48. package/packages/prefetch/src/index.ts +30 -9
  49. package/packages/router/src/global.ts +17 -4
  50. package/packages/router/src/index.ts +25 -18
  51. package/packages/router/src/structure/Route.ts +2 -1
  52. package/packages/router/src/structure/RouteNode.ts +8 -6
  53. package/packages/router/src/structure/RouteSlot.ts +15 -2
  54. package/packages/router/src/structure/Router.ts +28 -19
  55. package/packages/router/src/structure/RouterConstructor.ts +5 -5
  56. package/packages/router/src/types.ts +2 -2
  57. package/packages/signal/README.md +28 -48
  58. package/packages/signal/src/index.ts +61 -38
  59. package/packages/signal/src/structure/Signal.ts +40 -7
  60. package/packages/store/src/structure/Store.ts +1 -1
  61. package/packages/ui/package.json +2 -1
  62. package/packages/ui/src/icon/check.svg.ts +1 -0
  63. package/packages/ui/src/icon/x.svg.ts +1 -0
  64. package/packages/ui/src/index.ts +9 -2
  65. package/packages/ui/src/lib/combobox_style.ts +20 -0
  66. package/packages/ui/src/lib/hover.ts +2 -0
  67. package/packages/ui/src/structure/Badge.ts +10 -1
  68. package/packages/ui/src/structure/Button.ts +54 -27
  69. package/packages/ui/src/structure/Card.ts +3 -4
  70. package/packages/ui/src/structure/Combobox/Combobox.ts +312 -0
  71. package/packages/ui/src/structure/Combobox/ComboboxChips.ts +178 -0
  72. package/packages/ui/src/structure/Combobox/ComboboxList.ts +209 -0
  73. package/packages/ui/src/structure/ContextMenu.ts +89 -0
  74. package/packages/ui/src/structure/Field.ts +109 -0
  75. package/packages/ui/src/structure/Input.ts +29 -0
  76. package/packages/ui/src/structure/Select/Select.ts +18 -8
  77. package/packages/ui/src/structure/Select/SelectContent.ts +6 -1
  78. package/packages/ui/src/structure/Select/SelectItem.ts +2 -1
  79. package/packages/ui/src/structure/Slideshow.ts +2 -2
  80. package/packages/ui/src/structure/Switch.ts +45 -0
  81. package/packages/ui/src/structure/Tabs.ts +3 -3
  82. package/packages/ui/src/structure/Toggle.ts +155 -0
  83. package/packages/ui/src/structure/Waterfall.ts +1 -1
  84. package/packages/ui/src/structure/WaterfallItem.ts +1 -1
  85. package/packages/utils/src/lib/utils.ts +30 -8
  86. package/packages/utils/src/structure/UID.ts +1 -1
  87. package/packages/widget/src/index.ts +29 -9
  88. package/packages/widget/src/structure/Widget.ts +7 -3
  89. package/packages/ui/src/structure/TextBlock.ts +0 -11
@@ -0,0 +1,89 @@
1
+ import { toCSS } from "#lib/toCSS";
2
+ import { ElementProto, onclient } from "@amateras/core";
3
+ import { _instanceof, _null } from "@amateras/utils";
4
+
5
+ export class ContextMenu extends ElementProto {
6
+ static tagname = 'context-menu';
7
+ private clickListener: ((e: MouseEvent) => void) | null = _null;
8
+ constructor(props: $.Props, layout?: $.Layout<ContextMenu>) {
9
+ super(ContextMenu.tagname, props, layout);
10
+ }
11
+
12
+ static {
13
+ $.style(this, toCSS(this.tagname, {
14
+ display: 'block',
15
+ position: 'fixed',
16
+ top: '0',
17
+ left: '0',
18
+ maxHeight: '50dvh',
19
+ overflowY: 'auto',
20
+ boxSizing: 'border-box',
21
+ border: '1px solid var(--input)',
22
+ background: 'oklch(from var(--bg) l c h)',
23
+ padding: 'calc(var(--spacing) * 2) calc(var(--spacing) * 1.25)',
24
+ borderRadius: 'var(--radius)',
25
+ userSelect: 'none',
26
+
27
+ scrollbarWidth: 'thin',
28
+ scrollbarColor: 'var(--input) transparent',
29
+ }))
30
+ }
31
+
32
+ open(e: MouseEvent) {
33
+ if (onclient()) {
34
+ this.build();
35
+ this.style({
36
+ top: `${e.y}px`,
37
+ left: `${e.x}px`
38
+ })
39
+ this.clickListener = (e) => {
40
+ if (e.target === this.node) return;
41
+ if (e.target && this.node?.contains(e.target as Node)) return;
42
+ this.close();
43
+ }
44
+ setTimeout(() => window.addEventListener('click', this.clickListener!), 1)
45
+ document.body.append(...this.toDOM());
46
+ }
47
+ }
48
+
49
+ close(dispose = true) {
50
+ this.removeNode();
51
+ if (this.clickListener) window.removeEventListener('click', this.clickListener);
52
+ this.clickListener = _null;
53
+ if (dispose) this.dispose();
54
+ }
55
+ }
56
+
57
+ export class ContextMenuItem extends ElementProto {
58
+ static tagname = 'context-menu-item'
59
+ private $menu: null | ContextMenu = _null;
60
+ constructor(props: $.Props, layout?: $.Layout<ContextMenuItem>) {
61
+ super(ContextMenuItem.tagname, props, layout);
62
+ this.on('click', () => this.$menu?.close())
63
+ }
64
+
65
+ static {
66
+ $.style(this, toCSS(this.tagname, {
67
+ display: 'block',
68
+ boxSizing: 'border-box',
69
+ padding: 'calc(var(--spacing) * 2) calc(var(--spacing) * 1.25)',
70
+ borderRadius: 'var(--radius)',
71
+ fontSize: '0.875rem',
72
+ fontWeight: '500',
73
+ lineHeight: '1',
74
+
75
+ '&:hover, :not(:has(select-item:hover)) &:focus': {
76
+ background: 'oklch(from var(--input) l c h / .1)'
77
+ },
78
+ '&:focus': {
79
+ outline: 'none'
80
+ }
81
+ }))
82
+ }
83
+
84
+ override build(cascading?: boolean): this {
85
+ super.build(cascading);
86
+ this.$menu = this.findAbove(proto => _instanceof(proto, ContextMenu));
87
+ return this;
88
+ }
89
+ }
@@ -0,0 +1,109 @@
1
+ import { toCSS } from "#lib/toCSS";
2
+ import { ElementProto } from "@amateras/core";
3
+ import { _instanceof, isUndefined } from "@amateras/utils";
4
+
5
+ export interface FieldProps {
6
+ for?: OrSignal<string>;
7
+ direction?: 'vertical' | 'horizontal'
8
+ }
9
+
10
+ export class Field extends ElementProto<HTMLElement> {
11
+ static tagname = 'field';
12
+ constructor(props: $.Props<FieldProps>, layout?: $.Layout<Field>) {
13
+ super(Field.tagname, props, layout);
14
+ }
15
+
16
+ static {
17
+ $.style(this, toCSS(this.tagname, {
18
+ display: 'flex',
19
+ flexDirection: 'column',
20
+ gap: 'calc(var(--spacing) * 2)',
21
+ width: '100%',
22
+
23
+ '&[direction="horizontal"]': {
24
+ flexDirection: 'row',
25
+ placeItems: 'center',
26
+ width: 'unset'
27
+ }
28
+ }))
29
+ }
30
+
31
+ override props({ for: htmlFor, ...props }: $.Props<FieldProps>): void {
32
+ super.props(props);
33
+ this.for(htmlFor);
34
+ }
35
+
36
+ override build(cascading?: boolean): this {
37
+ super.build(cascading);
38
+ this.setFor();
39
+ return this;
40
+ }
41
+
42
+ for(): string | null;
43
+ for(name?: OrSignal<string>): void
44
+ for(name?: OrSignal<string>) {
45
+ if (!arguments.length) return this.attr('for');
46
+ if (isUndefined(name)) return;
47
+ $.resolve(name, name => {
48
+ this.attr('for', name);
49
+ this.setFor();
50
+ });
51
+ }
52
+
53
+ private setFor() {
54
+ const name = this.for();
55
+ this.findBelow(proto => {
56
+ if (_instanceof(proto, ElementProto)) {
57
+ if (proto.tagname === 'input') proto.attr('id', name);
58
+ if (proto.tagname === 'label') proto.attr('for', name)
59
+ }
60
+ })
61
+ }
62
+ }
63
+
64
+ export class FieldLabel extends ElementProto<HTMLElement> {
65
+ static tagname = 'label';
66
+ constructor(props: $.Props, layout?: $.Layout<FieldLabel>) {
67
+ super(FieldLabel.tagname, props, layout);
68
+ }
69
+
70
+ static {
71
+ $.style(this, toCSS(this.tagname, {
72
+ 'field &': {
73
+ display: 'block',
74
+ userSelect: 'none',
75
+ color: 'var(--fg)',
76
+ fontWeight: 'var(--font-weight-medium)',
77
+ fontSize: 'var(--text-sm)',
78
+ lineHeight: 'var(--text-sm)',
79
+ flexShrink: '0'
80
+ },
81
+
82
+ 'field[disabled] &': {
83
+ pointerEvents: 'none'
84
+ }
85
+ }))
86
+ }
87
+ }
88
+
89
+ export class FieldDescription extends ElementProto<HTMLElement> {
90
+ static tagname = 'field-description';
91
+ constructor(props: $.Props, layout?: $.Layout<FieldDescription>) {
92
+ super(FieldDescription.tagname, props, layout);
93
+ }
94
+
95
+ static {
96
+ $.style(this, toCSS(this.tagname, {
97
+ 'field &': {
98
+ display: 'block',
99
+ color: 'var(--muted)',
100
+ fontWeight: 'var(--font-weight-medium)',
101
+ fontSize: 'var(--text-sm)'
102
+ },
103
+
104
+ 'field[disabled] &': {
105
+ pointerEvents: 'none'
106
+ }
107
+ }))
108
+ }
109
+ }
@@ -0,0 +1,29 @@
1
+ import { toCSS } from "#lib/toCSS";
2
+ import { ElementProto } from "@amateras/core";
3
+
4
+ export class Input extends ElementProto<HTMLInputElement> {
5
+ static tagname = 'input';
6
+ constructor(props: $.Props<{}, HTMLInputElement>, layout?: $.Layout<Input>) {
7
+ super(Input.tagname, {ui: 'input', ...props}, layout)
8
+ }
9
+
10
+ static {
11
+ $.style(this, toCSS('input[ui="input"]', {
12
+ borderRadius: 'var(--radius)',
13
+ height: 'calc(var(--spacing) * 8)',
14
+ transition: '0.2s all ease',
15
+ fontFamily: 'inherit',
16
+ fontSize: 'var(--text-sm)',
17
+ padding: `0 calc(var(--spacing) * 2.5)`,
18
+
19
+ border: '1px solid oklch(from var(--input) l c h / .2)',
20
+ background: 'oklch(from var(--input) l c h / .025)',
21
+ color: 'oklch(from var(--fg) l c h / .9)',
22
+ outline: '0.2rem solid transparent',
23
+
24
+ '&:focus-visible': {
25
+ outlineColor: 'var(--border)'
26
+ },
27
+ }))
28
+ }
29
+ }
@@ -1,7 +1,7 @@
1
- import { ElementProto, onclient } from "@amateras/core";
2
- import { _null, isUndefined } from "@amateras/utils";
1
+ import { ElementProto, onclient, Proto } from "@amateras/core";
2
+ import { _instanceof, _null, isUndefined } from "@amateras/utils";
3
3
  import type { SelectContent } from "./SelectContent";
4
- import type { SelectItem } from "./SelectItem";
4
+ import { SelectItem } from "./SelectItem";
5
5
  import type { SelectTrigger } from "./SelectTrigger";
6
6
  import { float, type FloatDisconnect } from "#lib/float";
7
7
  import type { SelectValue } from "./SelectValue";
@@ -37,21 +37,22 @@ export class Select extends ElementProto {
37
37
 
38
38
  override props({value, disabled, ...props}: $.Props<SelectProps>): void {
39
39
  super.props(props);
40
- if (disabled) this.disabled(disabled);
40
+ this.disabled(disabled);
41
41
  this.value(value);
42
42
  }
43
43
 
44
44
  disabled(): boolean;
45
- disabled(bool: OrSignal<boolean>): void
45
+ disabled(bool?: OrSignal<boolean>): void
46
46
  disabled(bool?: OrSignal<boolean>) {
47
47
  if (!arguments.length) return this.attr('disabled') === '';
48
+ if (isUndefined(bool)) return;
48
49
  $.resolve(bool, bool => {
49
50
  this.attr('disabled', bool ? '' : _null);
50
51
  });
51
52
  }
52
53
 
53
54
  value(): any;
54
- value(val: OrSignal<any>): void;
55
+ value(val?: OrSignal<any>): void;
55
56
  value(val?: OrSignal<any>) {
56
57
  if (!arguments.length) return this.#value;
57
58
  if (isUndefined(val)) return;
@@ -60,6 +61,7 @@ export class Select extends ElementProto {
60
61
  let $item = this.itemMap.get(val);
61
62
  this.selected = $item ?? _null;
62
63
  this.$value?.render();
64
+ this.dispatch('selectvalue', [this, val], {bubbles: true})
63
65
  })
64
66
  this.node?.dispatchEvent(new Event('select-value'));
65
67
  }
@@ -74,7 +76,7 @@ export class Select extends ElementProto {
74
76
  if (e.target && this.$content?.node?.contains(e.target as Node)) return;
75
77
  this.close();
76
78
  }
77
- this.selected?.node?.focus();
79
+ this.selected?.node?.focus() ?? this.$content?.findBelow<SelectItem>(proto => _instanceof(proto, SelectItem))?.node?.focus();
78
80
  window.addEventListener('click', this.clickListener)
79
81
  }
80
82
  }
@@ -88,7 +90,7 @@ export class Select extends ElementProto {
88
90
  this.disconnect = _null;
89
91
  }
90
92
 
91
- override toDOM(children?: boolean): HTMLElement[] {
93
+ override toDOM(children = true): HTMLElement[] {
92
94
  super.toDOM(false);
93
95
  if (children && this.$trigger) {
94
96
  this.node?.append(...this.$trigger.toDOM());
@@ -102,4 +104,12 @@ export class Select extends ElementProto {
102
104
  let triggerHTML = this.$trigger?.toString() ?? '';
103
105
  return this.parseHTML({children: triggerHTML});
104
106
  }
107
+ }
108
+
109
+ declare global {
110
+ export namespace $ {
111
+ export interface ProtoEventMap<P extends Proto> {
112
+ selectvalue: [$select: Select, value: any]
113
+ }
114
+ }
105
115
  }
@@ -15,13 +15,18 @@ export class SelectContent extends ElementProto {
15
15
  position: 'absolute',
16
16
  top: '0',
17
17
  left: '0',
18
+ maxHeight: '50dvh',
19
+ overflowY: 'auto',
18
20
  display: 'block',
19
21
  boxSizing: 'border-box',
20
22
  border: '1px solid var(--input)',
21
23
  background: 'oklch(from var(--bg) l c h)',
22
24
  padding: 'calc(var(--spacing) * 2) calc(var(--spacing) * 1.25)',
23
25
  borderRadius: 'var(--radius)',
24
- userSelect: 'none'
26
+ userSelect: 'none',
27
+
28
+ scrollbarWidth: 'thin',
29
+ scrollbarColor: 'var(--input) transparent',
25
30
  }))
26
31
  }
27
32
 
@@ -13,7 +13,7 @@ export class SelectItem extends ElementProto {
13
13
  $select: Select | null = _null;
14
14
  $content: SelectContent | null = _null;
15
15
  #value: any = _null;
16
- constructor(props: $.Props, layout?: $.Layout<Select>) {
16
+ constructor(props: $.Props<SelectItemProps>, layout?: $.Layout<Select>) {
17
17
  super(SelectItem.tagname, {tabindex: 0, ...props}, layout);
18
18
  this.on('click', () => {
19
19
  this.$select?.close();
@@ -109,6 +109,7 @@ export class SelectItem extends ElementProto {
109
109
  super.build(cascading);
110
110
  this.$select = this.findAbove<Select>(proto => _instanceof(proto, Select));
111
111
  this.$content = this.findAbove<SelectContent>(proto => _instanceof(proto, SelectContent));
112
+ if (this.$select && this.$select.value() === this.#value) this.$select.selected = this;
112
113
  this.$select?.itemMap.set(this.#value, this);
113
114
  return this;
114
115
  }
@@ -34,8 +34,8 @@ export class Slideshow extends ElementProto {
34
34
  this.autoplay = autoplay ?? false;
35
35
  this.autopause = autopause ?? true;
36
36
  this.animation = animation ?? _null;
37
- this.ondispose(() => this.pause());
38
- this.ondom(() => {
37
+ this.listen('dispose', () => this.pause());
38
+ this.listen('dom', () => {
39
39
  if (this.autoplay) this.play();
40
40
  })
41
41
 
@@ -0,0 +1,45 @@
1
+ import { toCSS } from "#lib/toCSS";
2
+ import { ElementProto } from "@amateras/core";
3
+
4
+ export class Switch extends ElementProto {
5
+ static tagname = 'input';
6
+ constructor(props: $.Props<{}, HTMLInputElement>, layout?: $.Layout<Switch>) {
7
+ super('input', {type: 'checkbox', ui: 'switch', ...props}, layout);
8
+ }
9
+
10
+ static {
11
+ $.style(this, toCSS('input[ui="switch"]', {
12
+ appearance: 'none',
13
+ display: 'inline-flex',
14
+ height: 'calc(var(--spacing) * 5)',
15
+ width: 'calc(var(--spacing) * 9)',
16
+ background: 'color-mix(in oklab, var(--input) 80%, transparent)',
17
+ border: 'none',
18
+ placeItems: 'center',
19
+ borderRadius: 'calc(var(--spacing) * 5)',
20
+ transition: 'all .3s ease',
21
+ padding: '0',
22
+
23
+ '&::after': {
24
+ content: '""',
25
+ display: 'block',
26
+ height: 'calc(var(--spacing) * 5)',
27
+ width: 'calc(var(--spacing) * 5)',
28
+ background: 'var(--fg)',
29
+ borderRadius: 'var(--radius-round)',
30
+ transition: 'all .3s ease'
31
+ },
32
+
33
+ '&:checked': {
34
+ background: 'var(--primary-bg)',
35
+
36
+ '&::after': {
37
+ height: 'calc(var(--spacing) * 4.5)',
38
+ width: 'calc(var(--spacing) * 4.5)',
39
+ translate: 'calc(115% - var(--spacing)) 0',
40
+ background: 'var(--bg)'
41
+ }
42
+ }
43
+ }))
44
+ }
45
+ }
@@ -5,12 +5,12 @@ export interface TabsOptions {
5
5
  targetId?: string;
6
6
  }
7
7
  export class Tabs extends ElementProto {
8
- targetId: string;
8
+ targetId: string | null;
9
9
  $container: TabsContainer | null = _null;
10
10
  triggers = new Map<string, TabTrigger>();
11
- constructor({targetId, ...props}: $.Props, layout?: $.Layout<Tabs>) {
11
+ constructor({targetId, ...props}: $.Props<TabsOptions>, layout?: $.Layout<Tabs>) {
12
12
  super('tabs', props, layout);
13
- this.targetId = targetId;
13
+ this.targetId = targetId ?? _null;
14
14
  }
15
15
 
16
16
  static {
@@ -0,0 +1,155 @@
1
+ import { pointerHoverMediaQuery } from "#lib/hover";
2
+ import { toCSS } from "#lib/toCSS";
3
+ import { ElementProto } from "@amateras/core";
4
+ import { _null, isNull, isUndefined } from "@amateras/utils";
5
+
6
+ export interface ToggleProps {
7
+ checked?: OrSignal<boolean>;
8
+ variant?: OrSignal<'primary' | 'outline' | 'secondary' | 'ghost' | 'destructive' | string & {}>;
9
+ size?: OrSignal<'base' | 'icon' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg' | string & {}>;
10
+ }
11
+
12
+ export class Toggle extends ElementProto<HTMLButtonElement> {
13
+ static tagname = 'button';
14
+ constructor(props: $.Props<ToggleProps, HTMLButtonElement>, layout?: $.Layout<Toggle>) {
15
+ super('button', { ui: 'toggle', ...props}, layout);
16
+
17
+ this.on('click', $$ => this.checked(!this.checked()))
18
+ }
19
+
20
+ static {
21
+ $.style(this, toCSS('button[ui="toggle"]', {
22
+ display: 'inline-flex',
23
+ placeContent: 'center',
24
+ placeItems: 'center',
25
+ padding: `0 calc(var(--spacing) * 2.5)`,
26
+ borderRadius: 'var(--radius)',
27
+ height: 'calc(var(--spacing) * 8)',
28
+ lineHeight: '1rem',
29
+ transition: '0.1s all ease',
30
+ fontWeight: 'var(--font-weight-medium)',
31
+ fontFamily: 'inherit',
32
+ outline: '0.1rem solid oklch(from var(--border) l c h / 0)',
33
+ userSelect: 'none',
34
+ gap: 'calc(var(--spacing) * 2.5)',
35
+ flexShrink: '0',
36
+
37
+ border: '1px solid oklch(from var(--input) l c h / .2)',
38
+ background: 'oklch(from var(--input) l c h / .025)',
39
+ color: 'oklch(from var(--fg) l c h / .8)',
40
+
41
+ [pointerHoverMediaQuery]: {
42
+ '&:not([disabled]):hover': {
43
+ background: 'oklch(from var(--input) l c h / .1)',
44
+ color: 'oklch(from var(--fg) l c h)',
45
+ },
46
+ },
47
+
48
+ '&[checked]': {
49
+ color: 'oklch(from var(--primary-fg) l c h)',
50
+ background: 'oklch(from var(--primary-bg) l c h / .8)',
51
+
52
+ [pointerHoverMediaQuery]: {
53
+ '&:not([disabled]):hover': {
54
+ background: 'oklch(from var(--primary-bg) l c h)',
55
+ color: 'oklch(from var(--primary-fg) l c h)',
56
+ },
57
+ },
58
+ },
59
+
60
+ '&[variant="primary"]': {
61
+ '&[checked]': {
62
+ color: 'oklch(from var(--primary-fg) l c h)',
63
+ background: 'var(--primary-bg)',
64
+ }
65
+ },
66
+
67
+ '&[variant="secondary"]': {
68
+ '&[checked]': {
69
+ color: 'oklch(from var(--secondary-fg) l c h)',
70
+ background: 'var(--secondary-bg)',
71
+ }
72
+ },
73
+
74
+ '&[variant="destructive"]': {
75
+ '&[checked]': {
76
+ color: 'oklch(from var(--destructive-fg) l c h)',
77
+ background: 'oklch(from var(--destructive-bg) l c h / .3)',
78
+ }
79
+ },
80
+
81
+ '&[variant="ghost"]': {
82
+ border: 'none',
83
+ color: 'oklch(from var(--fg) l c h)',
84
+ background: 'oklch(from var(--input) l c h / 0)',
85
+ '&[checked]': {
86
+ color: 'oklch(from var(--secondary-fg) l c h)',
87
+ background: 'var(--secondary-bg)',
88
+ }
89
+ },
90
+
91
+ '&[size="icon"]': {
92
+ width: 'calc(var(--spacing) * 8)',
93
+ padding: '0'
94
+ },
95
+
96
+ '&[size="xs"]': {
97
+ padding: 'calc(var(--spacing) * 2)',
98
+ fontSize: 'var(--text-xs)',
99
+ lineHeight: 'var(--text-xs)',
100
+ height: 'calc(var(--spacing) * 6)',
101
+ },
102
+
103
+ '&[size="icon-xs"]': {
104
+ width: 'calc(var(--spacing) * 6)',
105
+ height: 'calc(var(--spacing) * 6)',
106
+ padding: '0',
107
+
108
+ 'svg': {
109
+ width: 'calc(var(--spacing) * 3)',
110
+ height: 'calc(var(--spacing) * 3)'
111
+ }
112
+ },
113
+
114
+ '&[size="sm"]': {
115
+ height: 'calc(var(--spacing) * 7)'
116
+ },
117
+
118
+ '&[size="icon-sm"]': {
119
+ width: 'calc(var(--spacing) * 7)',
120
+ height: 'calc(var(--spacing) * 7)',
121
+ padding: '0',
122
+ },
123
+
124
+ '&[size="lg"]': {
125
+ height: 'calc(var(--spacing) * 9)'
126
+ },
127
+
128
+ '&[size="icon-lg"]': {
129
+ width: 'calc(var(--spacing) * 9)',
130
+ height: 'calc(var(--spacing) * 9)',
131
+ padding: '0',
132
+ },
133
+
134
+ '&[disabled]': {
135
+ opacity: '.5'
136
+ }
137
+ }))
138
+ }
139
+
140
+ override props({ checked, ...props }: $.Props<ToggleProps>): void {
141
+ super.props(props)
142
+ this.checked(checked);
143
+ }
144
+
145
+ checked(): boolean;
146
+ checked(bool?: OrSignal<boolean>): void;
147
+ checked(bool?: OrSignal<boolean>) {
148
+ if (!arguments.length) return !isNull(this.attr('checked'));
149
+ if (isUndefined(bool)) return;
150
+ $.resolve(bool, bool => {
151
+ this.attr('checked', bool ? '' : _null);
152
+ this.node?.dispatchEvent(new Event('input'))
153
+ })
154
+ }
155
+ }
@@ -27,7 +27,7 @@ export class Waterfall extends ElementProto {
27
27
  $.style(Waterfall, 'waterfall{display:block;position:relative;min-height:100dvh}')
28
28
  }
29
29
 
30
- override toDOM(children?: boolean): HTMLElement[] {
30
+ override toDOM(children = true): HTMLElement[] {
31
31
  super.toDOM(children);
32
32
  if (this.node) this.observer?.observe(this.node);
33
33
  return [this.node!]
@@ -12,7 +12,7 @@ export class WaterfallItem extends ElementProto {
12
12
  $.style(WaterfallItem, 'waterfall-item{display:block;position:absolute}')
13
13
  }
14
14
 
15
- override toDOM(children?: boolean): HTMLElement[] {
15
+ override toDOM(children = true): HTMLElement[] {
16
16
  super.toDOM(children);
17
17
  const $waterfall = this.findAbove<Waterfall>(proto => is(proto, Waterfall));
18
18
  this.node?.querySelectorAll('img').forEach(img => $waterfall?.observer?.observe(img));
@@ -48,6 +48,8 @@ interface map {
48
48
  }
49
49
  export const map: map = (arr: any, fn: any) => _Array_from(arr).map(fn);
50
50
 
51
+ export const filterValue = <T, V>(arr: Array<T>, ...values: V[]): Array<Exclude<T, V>> => arr.filter(item => !values.includes(item as any)) as any;
52
+
51
53
  // type check
52
54
  export const _typeof = (target: any, type: 'string' | 'number' | 'object' | 'boolean' | 'function' | 'bigint' | 'symbol' | 'undefined') => typeof target === type;
53
55
  export const isIncluded = <T, V>(target: T, args: V[]): target is V & T => args.includes(target as any);
@@ -104,9 +106,16 @@ export const sleep = async (ms: number) => new _Promise(resolve => setTimeout(re
104
106
 
105
107
  export const toArray = <T>(item: OrArray<T>): T[] => _instanceof(item, Array) ? item : [item];
106
108
 
107
- export const trycatch = <D>(callback: () => D): Result<D, Error> => {
109
+ export interface trycatch {
110
+ <D>(callback: () => D): D extends Promise<infer T> ? Promise<Result<T, Error>> : Result<D, Error>
111
+ }
112
+
113
+ //@ts-ignore
114
+ export const trycatch: trycatch = async (callback: any) => {
108
115
  try {
109
- return [callback(), _null];
116
+ const result = callback();
117
+ if (_instanceof(result, Promise)) return await result.then(res => [res, _null]);
118
+ return [result, _null];
110
119
  } catch (err) {
111
120
  return [_null, _instanceof(err, Error) ? err : new Error(_JSON_stringify(err))];
112
121
  }
@@ -114,17 +123,30 @@ export const trycatch = <D>(callback: () => D): Result<D, Error> => {
114
123
 
115
124
  export const uppercase = (str: string, start?: number, end?: number) => `${slice(str, 0, start)}${slice(str, start, end).toUpperCase()}${end ? slice(str, end) : ''}`
116
125
  const _URL = URL;
117
- export const toURL = (path: string | URL) => {
126
+ export const toURL = (path: string | URL, base = globalThis.origin ?? 'http://localhost') => {
118
127
  if (_instanceof(path, _URL)) return path;
119
- return new URL(path, globalThis.origin ?? 'http://localhost')
128
+ return new URL(path, base)
120
129
  }
121
130
 
122
131
  // object member compare
123
- export const isEqual = <T extends Object>(target: T, reference: Record<keyof T, any>, props?: (keyof T)[]) => {
132
+ export const isEqual = <T extends Object>(target: T, reference: any, props?: (keyof T)[]) => {
133
+ const propsSet = props ? new Set(props) : _null;
124
134
  for (let [ key, value ] of _Object_entries(target)) {
125
- if (props && !props.includes(key as any)) continue;
126
- if (reference[key as keyof T] === value) continue;
127
- else return false;
135
+ if (propsSet && !propsSet.has(key as any)) continue;
136
+ const targetValue = reference[key as keyof T];
137
+ if (targetValue !== value) {
138
+ if (isNull(targetValue) || isNull(value)) return false;
139
+ if (Number.isNaN(targetValue) && Number.isNaN(value)) continue;
140
+ if (isObject(targetValue) && isObject(value)) {
141
+ if (isArray(value) && isArray(targetValue)) {
142
+ if (value.length !== targetValue.length) return false;
143
+
144
+ }
145
+ if (isEqual(value, targetValue)) continue;
146
+ else return false
147
+ }
148
+ return false;
149
+ }
128
150
  }
129
151
  return true
130
152
  }
@@ -13,7 +13,7 @@ export class UID {
13
13
 
14
14
  static persistInProto(proto: Proto, key: string, options?: RandomIdOptions) {
15
15
  const id = UID.generate(key, options);
16
- proto.ondispose(() => UID.map.get(key)?.delete(id))
16
+ proto.listen('dispose', () => UID.map.get(key)?.delete(id))
17
17
  return id;
18
18
  }
19
19
  }