@uibit/form 0.1.1 → 0.2.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.
@@ -1,51 +1,57 @@
1
1
  import { Component, ChangeDetectionStrategy, ElementRef, input, effect, booleanAttribute, numberAttribute } from '@angular/core';
2
2
  import '@uibit/form';
3
3
  import type { Form as HTMLElementClass } from '@uibit/form';
4
-
5
4
  @Component({
6
- selector: 'uibit-form',
7
- template: '<ng-content></ng-content>',
8
- changeDetection: ChangeDetectionStrategy.OnPush,
9
- standalone: true
5
+ selector: 'uibit-form',
6
+ template: '<ng-content></ng-content>',
7
+ changeDetection: ChangeDetectionStrategy.OnPush,
8
+ standalone: true
10
9
  })
11
10
  export class NgxForm {
12
- constructor(private el: ElementRef<HTMLElementClass>) {
13
- effect(() => {
14
- if (this.el.nativeElement) {
15
- this.el.nativeElement.warnUnsaved = this.warnUnsaved();
16
- }
11
+ constructor(private el: ElementRef<HTMLElementClass>){
12
+ effect(()=>{
13
+ if (this.el.nativeElement) {
14
+ this.el.nativeElement.warnUnsaved = this.warnUnsaved();
15
+ }
16
+ });
17
+ effect(()=>{
18
+ if (this.el.nativeElement) {
19
+ this.el.nativeElement.step = this.step();
20
+ }
21
+ });
22
+ effect(()=>{
23
+ if (this.el.nativeElement) {
24
+ this.el.nativeElement.stepsCount = this.stepsCount();
25
+ }
26
+ });
27
+ effect(()=>{
28
+ if (this.el.nativeElement) {
29
+ this.el.nativeElement.dirty = this.dirty();
30
+ }
31
+ });
32
+ effect(()=>{
33
+ if (this.el.nativeElement) {
34
+ this.el.nativeElement.status = this.status();
35
+ }
36
+ });
37
+ effect(()=>{
38
+ if (this.el.nativeElement) {
39
+ this.el.nativeElement.locale = this.locale();
40
+ }
41
+ });
42
+ }
43
+ readonly warnUnsaved = input<boolean, any>(false, {
44
+ transform: booleanAttribute
17
45
  });
18
- effect(() => {
19
- if (this.el.nativeElement) {
20
- this.el.nativeElement.step = this.step();
21
- }
46
+ readonly step = input<number, any>(1, {
47
+ transform: numberAttribute
22
48
  });
23
- effect(() => {
24
- if (this.el.nativeElement) {
25
- this.el.nativeElement.stepsCount = this.stepsCount();
26
- }
49
+ readonly stepsCount = input<number, any>(0, {
50
+ transform: numberAttribute
27
51
  });
28
- effect(() => {
29
- if (this.el.nativeElement) {
30
- this.el.nativeElement.dirty = this.dirty();
31
- }
52
+ readonly dirty = input<boolean, any>(false, {
53
+ transform: booleanAttribute
32
54
  });
33
- effect(() => {
34
- if (this.el.nativeElement) {
35
- this.el.nativeElement.status = this.status();
36
- }
37
- });
38
- effect(() => {
39
- if (this.el.nativeElement) {
40
- this.el.nativeElement.locale = this.locale();
41
- }
42
- });
43
- }
44
-
45
- readonly warnUnsaved = input<boolean, any>(false, { transform: booleanAttribute });
46
- readonly step = input<number, any>(1, { transform: numberAttribute });
47
- readonly stepsCount = input<number, any>(0, { transform: numberAttribute });
48
- readonly dirty = input<boolean, any>(false, { transform: booleanAttribute });
49
- readonly status = input<'idle' | 'pending' | 'success' | 'error', any>('idle');
50
- readonly locale = input<string, any>('');
55
+ readonly status = input<'idle' | 'pending' | 'success' | 'error'>('idle');
56
+ readonly locale = input<string>('');
51
57
  }
@@ -4,7 +4,15 @@ import '@uibit/form';
4
4
  declare global {
5
5
  namespace astroHTML.JSX {
6
6
  interface IntrinsicElements {
7
- 'uibit-form': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes;
7
+ 'uibit-form': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes & {
8
+ warnUnsaved?: boolean;
9
+ step?: number;
10
+ stepsCount?: number;
11
+ dirty?: boolean;
12
+ status?: 'idle' | 'pending' | 'success' | 'error';
13
+ locale?: string;
14
+
15
+ };
8
16
  }
9
17
  }
10
18
  }
@@ -12,6 +12,7 @@ declare module 'preact' {
12
12
  dirty?: boolean;
13
13
  status?: 'idle' | 'pending' | 'success' | 'error';
14
14
  locale?: string;
15
+
15
16
  };
16
17
  }
17
18
  }
@@ -0,0 +1,105 @@
1
+ import React, { useRef, useEffect, useLayoutEffect, useImperativeHandle } from 'react';
2
+ import type { Form as HTMLElementClass } from '@uibit/form';
3
+ import '@uibit/form';
4
+ const useTypeOfLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
5
+ export interface FormProps extends Omit<React.HTMLAttributes<HTMLElementClass>, 'warnUnsaved' | 'step' | 'stepsCount' | 'dirty' | 'status' | 'locale'> {
6
+ children?: React.ReactNode;
7
+ warnUnsaved?: boolean;
8
+ step?: number;
9
+ stepsCount?: number;
10
+ dirty?: boolean;
11
+ status?: 'idle' | 'pending' | 'success' | 'error';
12
+ locale?: string;
13
+ }
14
+ export const Form = ({ ref, children, ...props }: FormProps & {
15
+ ref?: React.Ref<HTMLElementClass>;
16
+ })=>{
17
+ const innerRef = useRef<HTMLElementClass>(null);
18
+ const propsRef = useRef(props);
19
+ propsRef.current = props;
20
+ useImperativeHandle(ref, ()=>innerRef.current!);
21
+ useTypeOfLayoutEffect(()=>{
22
+ const element = innerRef.current;
23
+ if (element && props.warnUnsaved !== undefined) {
24
+ (element as any).warnUnsaved = props.warnUnsaved;
25
+ }
26
+ }, [
27
+ props.warnUnsaved
28
+ ]);
29
+ useTypeOfLayoutEffect(()=>{
30
+ const element = innerRef.current;
31
+ if (element && props.step !== undefined) {
32
+ (element as any).step = props.step;
33
+ }
34
+ }, [
35
+ props.step
36
+ ]);
37
+ useTypeOfLayoutEffect(()=>{
38
+ const element = innerRef.current;
39
+ if (element && props.stepsCount !== undefined) {
40
+ (element as any).stepsCount = props.stepsCount;
41
+ }
42
+ }, [
43
+ props.stepsCount
44
+ ]);
45
+ useTypeOfLayoutEffect(()=>{
46
+ const element = innerRef.current;
47
+ if (element && props.dirty !== undefined) {
48
+ (element as any).dirty = props.dirty;
49
+ }
50
+ }, [
51
+ props.dirty
52
+ ]);
53
+ useTypeOfLayoutEffect(()=>{
54
+ const element = innerRef.current;
55
+ if (element && props.status !== undefined) {
56
+ (element as any).status = props.status;
57
+ }
58
+ }, [
59
+ props.status
60
+ ]);
61
+ useTypeOfLayoutEffect(()=>{
62
+ const element = innerRef.current;
63
+ if (element && props.locale !== undefined) {
64
+ (element as any).locale = props.locale;
65
+ }
66
+ }, [
67
+ props.locale
68
+ ]);
69
+ const domProps = {
70
+ ...props
71
+ };
72
+ const customPropNames = [
73
+ 'warnUnsaved',
74
+ 'step',
75
+ 'stepsCount',
76
+ 'dirty',
77
+ 'status',
78
+ 'locale'
79
+ ];
80
+ for (const key of customPropNames){
81
+ delete (domProps as any)[key];
82
+ }
83
+ return React.createElement('uibit-form', {
84
+ ref: innerRef,
85
+ ...domProps
86
+ }, children);
87
+ };
88
+ declare global {
89
+ namespace React {
90
+ namespace JSX {
91
+ interface IntrinsicElements {
92
+ 'uibit-form': React.ClassAttributes<HTMLElementClass> & React.HTMLAttributes<HTMLElementClass> & {
93
+ children?: React.ReactNode;
94
+ class?: string;
95
+ warnUnsaved?: boolean;
96
+ step?: number;
97
+ stepsCount?: number;
98
+ dirty?: boolean;
99
+ status?: 'idle' | 'pending' | 'success' | 'error';
100
+ locale?: string;
101
+ };
102
+ }
103
+ }
104
+ }
105
+ }
@@ -11,6 +11,7 @@ declare module '@stencil/core' {
11
11
  dirty?: boolean;
12
12
  status?: 'idle' | 'pending' | 'success' | 'error';
13
13
  locale?: string;
14
+
14
15
  };
15
16
  }
16
17
  }
@@ -1,19 +1,20 @@
1
1
  <script lang="ts">
2
2
  import '@uibit/form';
3
3
  import type { Form as HTMLElementClass } from '@uibit/form';
4
-
5
- let {
6
- warnUnsaved = undefined,
7
- step = undefined,
8
- stepsCount = undefined,
9
- dirty = undefined,
10
- status = undefined,
11
- locale = undefined,
12
- success = undefined,
13
- error = undefined,
14
- children
4
+
5
+ let {
6
+ warnUnsaved = $bindable(),
7
+ step = $bindable(),
8
+ stepsCount = $bindable(),
9
+ dirty = $bindable(),
10
+ status = $bindable(),
11
+ locale = $bindable(),
12
+ success = undefined,
13
+ error = undefined,
14
+ children,
15
+ ...restProps
15
16
  } = $props<{
16
- children?: any;
17
+ children?: import('svelte').Snippet;
17
18
  warnUnsaved?: boolean;
18
19
  step?: number;
19
20
  stepsCount?: number;
@@ -22,34 +23,71 @@
22
23
  locale?: string;
23
24
  success?: import('svelte').Snippet;
24
25
  error?: import('svelte').Snippet;
26
+ [key: string]: any;
25
27
  }>();
26
-
27
- let elementRef: HTMLElementClass | null = $state(null);
28
-
29
- $effect(() => {
30
- if (elementRef && warnUnsaved !== undefined) {
28
+
29
+ let elementRef: HTMLElementClass | null = $state(null);
30
+
31
+ $effect(() => {
32
+ if (elementRef && warnUnsaved !== undefined && elementRef.warnUnsaved !== warnUnsaved) {
31
33
  elementRef.warnUnsaved = warnUnsaved;
32
34
  }
33
- if (elementRef && step !== undefined) {
35
+ if (elementRef && step !== undefined && elementRef.step !== step) {
34
36
  elementRef.step = step;
35
37
  }
36
- if (elementRef && stepsCount !== undefined) {
38
+ if (elementRef && stepsCount !== undefined && elementRef.stepsCount !== stepsCount) {
37
39
  elementRef.stepsCount = stepsCount;
38
40
  }
39
- if (elementRef && dirty !== undefined) {
41
+ if (elementRef && dirty !== undefined && elementRef.dirty !== dirty) {
40
42
  elementRef.dirty = dirty;
41
43
  }
42
- if (elementRef && status !== undefined) {
44
+ if (elementRef && status !== undefined && elementRef.status !== status) {
43
45
  elementRef.status = status;
44
46
  }
45
- if (elementRef && locale !== undefined) {
47
+ if (elementRef && locale !== undefined && elementRef.locale !== locale) {
46
48
  elementRef.locale = locale;
47
49
  }
48
50
  });
49
-
51
+
52
+ $effect(() => {
53
+ const element = elementRef;
54
+ if (!element) return;
55
+
56
+ const handleEvent = () => {
57
+ if (warnUnsaved !== element.warnUnsaved) {
58
+ warnUnsaved = element.warnUnsaved as any;
59
+ }
60
+ if (step !== element.step) {
61
+ step = element.step as any;
62
+ }
63
+ if (stepsCount !== element.stepsCount) {
64
+ stepsCount = element.stepsCount as any;
65
+ }
66
+ if (dirty !== element.dirty) {
67
+ dirty = element.dirty as any;
68
+ }
69
+ if (status !== element.status) {
70
+ status = element.status as any;
71
+ }
72
+ if (locale !== element.locale) {
73
+ locale = element.locale as any;
74
+ }
75
+ };
76
+
77
+ const events = ['change', 'input'];
78
+ for (const event of events) {
79
+ element.addEventListener(event, handleEvent);
80
+ }
81
+ return () => {
82
+ for (const event of events) {
83
+ element.removeEventListener(event, handleEvent);
84
+ }
85
+ };
86
+ });
87
+
50
88
  </script>
51
89
 
52
- <uibit-form bind:this={elementRef} {...$$restProps}>
90
+ <uibit-form bind:this={elementRef} {...restProps}>
53
91
  {#if success}
54
92
  <div slot="success">
55
93
  {@render success()}
@@ -1,27 +1,120 @@
1
- import { defineComponent, h } from 'vue';
1
+ import { defineComponent, h, ref, watch } from 'vue';
2
2
  import type { Form as HTMLElementClass } from '@uibit/form';
3
3
  import '@uibit/form';
4
-
5
4
  export const Form = defineComponent({
6
- name: 'Form',
7
- props: {
8
- warnUnsaved: { type: [String, Number, Boolean, Array, Object] as any },
9
- step: { type: [String, Number, Boolean, Array, Object] as any },
10
- stepsCount: { type: [String, Number, Boolean, Array, Object] as any },
11
- dirty: { type: [String, Number, Boolean, Array, Object] as any },
12
- status: { type: [String, Number, Boolean, Array, Object] as any },
13
- locale: { type: [String, Number, Boolean, Array, Object] as any }
14
- },
15
- emits: [],
16
- setup(props, { slots, emit }) {
17
- return () => {
18
- const eventListeners: Record<string, any> = {};
19
-
20
-
21
- return h('uibit-form', {
22
- ...props,
23
- ...eventListeners
24
- }, slots.default?.());
25
- };
26
- }
5
+ name: 'Form',
6
+ props: {
7
+ warnUnsaved: {
8
+ type: [
9
+ String,
10
+ Number,
11
+ Boolean,
12
+ Array,
13
+ Object
14
+ ] as any
15
+ },
16
+ step: {
17
+ type: [
18
+ String,
19
+ Number,
20
+ Boolean,
21
+ Array,
22
+ Object
23
+ ] as any
24
+ },
25
+ stepsCount: {
26
+ type: [
27
+ String,
28
+ Number,
29
+ Boolean,
30
+ Array,
31
+ Object
32
+ ] as any
33
+ },
34
+ dirty: {
35
+ type: [
36
+ String,
37
+ Number,
38
+ Boolean,
39
+ Array,
40
+ Object
41
+ ] as any
42
+ },
43
+ status: {
44
+ type: [
45
+ String,
46
+ Number,
47
+ Boolean,
48
+ Array,
49
+ Object
50
+ ] as any
51
+ },
52
+ locale: {
53
+ type: [
54
+ String,
55
+ Number,
56
+ Boolean,
57
+ Array,
58
+ Object
59
+ ] as any
60
+ }
61
+ },
62
+ emits: [],
63
+ setup (props, { slots, emit }) {
64
+ const elementRef = ref<HTMLElementClass | null>(null);
65
+ if (false) {
66
+ watch(()=>props.modelValue, (newVal)=>{
67
+ if (elementRef.value && newVal !== undefined && elementRef.value.value !== newVal) {
68
+ elementRef.value.value = newVal;
69
+ }
70
+ });
71
+ watch(()=>props.value, (newVal)=>{
72
+ if (elementRef.value && newVal !== undefined && elementRef.value.value !== newVal) {
73
+ elementRef.value.value = newVal;
74
+ }
75
+ });
76
+ }
77
+ watch(()=>props.warnUnsaved, (newVal)=>{
78
+ if (elementRef.value && newVal !== undefined) {
79
+ (elementRef.value as any).warnUnsaved = newVal;
80
+ }
81
+ });
82
+ watch(()=>props.step, (newVal)=>{
83
+ if (elementRef.value && newVal !== undefined) {
84
+ (elementRef.value as any).step = newVal;
85
+ }
86
+ });
87
+ watch(()=>props.stepsCount, (newVal)=>{
88
+ if (elementRef.value && newVal !== undefined) {
89
+ (elementRef.value as any).stepsCount = newVal;
90
+ }
91
+ });
92
+ watch(()=>props.dirty, (newVal)=>{
93
+ if (elementRef.value && newVal !== undefined) {
94
+ (elementRef.value as any).dirty = newVal;
95
+ }
96
+ });
97
+ watch(()=>props.status, (newVal)=>{
98
+ if (elementRef.value && newVal !== undefined) {
99
+ (elementRef.value as any).status = newVal;
100
+ }
101
+ });
102
+ watch(()=>props.locale, (newVal)=>{
103
+ if (elementRef.value && newVal !== undefined) {
104
+ (elementRef.value as any).locale = newVal;
105
+ }
106
+ });
107
+ return ()=>{
108
+ const eventListeners: Record<string, any> = {};
109
+ const mergedProps = {
110
+ ...props,
111
+ ...eventListeners,
112
+ ref: elementRef
113
+ };
114
+ if (false && props.modelValue !== undefined) {
115
+ (mergedProps as any).value = props.modelValue;
116
+ }
117
+ return h('uibit-form', mergedProps, slots.default?.());
118
+ };
119
+ }
27
120
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uibit/form",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "A composable, declarative wrapper around native HTML form elements with validation, submit lifecycle states, and wizard navigation support.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,7 +11,7 @@
11
11
  "import": "./dist/index.js"
12
12
  },
13
13
  "./custom-elements.json": "./custom-elements.json",
14
- "./react": "./dist/frameworks/react/index.d.ts",
14
+ "./react": "./dist/frameworks/react/index.ts",
15
15
  "./vue": "./dist/frameworks/vue/index.ts",
16
16
  "./svelte": "./dist/frameworks/svelte/index.svelte",
17
17
  "./angular": "./dist/frameworks/angular/index.ts",
@@ -46,7 +46,7 @@
46
46
  "@types/node": "^20.19.43",
47
47
  "lit": "^3.3.3",
48
48
  "typescript": "7.0.2",
49
- "@uibit/codegen": "0.1.0"
49
+ "@uibit/codegen": "0.2.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "lit": "^3.0.0",
@@ -1,22 +0,0 @@
1
- import type { HTMLAttributes, ClassAttributes } from 'react';
2
- import type { Form as HTMLElementClass } from '@uibit/form';
3
- import '@uibit/form';
4
-
5
- declare global {
6
- namespace React {
7
- namespace JSX {
8
- interface IntrinsicElements {
9
- 'uibit-form': ClassAttributes<HTMLElementClass> & HTMLAttributes<HTMLElementClass> & {
10
- children?: React.ReactNode;
11
- class?: string;
12
- warnUnsaved?: boolean;
13
- step?: number;
14
- stepsCount?: number;
15
- dirty?: boolean;
16
- status?: 'idle' | 'pending' | 'success' | 'error';
17
- locale?: string;
18
- };
19
- }
20
- }
21
- }
22
- }