@featherk/ui 0.9.0 → 0.9.2

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/README.md CHANGED
@@ -4,14 +4,14 @@ Vue UI component package for FeatherK.
4
4
 
5
5
  ## Components Overview
6
6
 
7
- - **Address**: `USAddress` - Complete US address form component with delayed validation orchestration and normalized submit payloads.
7
+ - **Address**: `USAddress` - Presentational US address renderer driven by a parent-owned `useUSAddress` view model.
8
8
  - Guide: [docs/address/USAddress.md](https://github.com/NantHealth/featherk/blob/integration/packages/ui/docs/address/USAddress.md)
9
9
 
10
10
  ## Importing Components
11
11
 
12
12
  ```ts
13
13
  import { USAddress } from "@featherk/ui";
14
- import type { USAddressSubmitPayload } from "@featherk/composables/address";
14
+ import { useUSAddress } from "@featherk/composables/address";
15
15
  ```
16
16
 
17
17
  ## Usage
@@ -20,31 +20,55 @@ Component styles ship with `@featherk/ui` and are automatically loaded from the
20
20
 
21
21
  ```vue
22
22
  <script setup lang="ts">
23
+ import { ref, toRef } from "vue";
23
24
  import { USAddress } from "@featherk/ui";
24
- import type { USAddressSubmitPayload } from "@featherk/composables/address";
25
+ import { useUSAddress } from "@featherk/composables/address";
25
26
 
26
- function onValidSubmit(payload: USAddressSubmitPayload) {
27
- console.log(payload);
27
+ const billingFieldsetRef = ref<HTMLFieldSetElement | null>(null);
28
+
29
+ const billingAddress = useUSAddress({
30
+ fieldsetRef: toRef(() => billingFieldsetRef.value),
31
+ addressId: "billing-address",
32
+ });
33
+
34
+ const onFieldsetFocusout = (event: FocusEvent) => {
35
+ billingAddress.onFieldsetFocusout(event);
36
+ };
37
+
38
+ function onReset() {
39
+ billingAddress.reset();
28
40
  }
29
41
 
30
- function onInvalidSubmit() {
31
- console.log("invalid");
42
+ async function onSubmit() {
43
+ const result = await billingAddress.validateForSubmit();
44
+ if (!result.isValid) return;
45
+
46
+ console.log(result.payload);
32
47
  }
33
48
  </script>
34
49
 
35
50
  <template>
36
- <USAddress @valid-submit="onValidSubmit" @invalid-submit="onInvalidSubmit" />
51
+ <form @submit.prevent="onSubmit">
52
+ <fieldset ref="billingFieldsetRef" @focusout="onFieldsetFocusout">
53
+ <USAddress id="billing-address" :address="billingAddress" />
54
+
55
+ <label for="billing-attn">Attention</label>
56
+ <input id="billing-attn" type="text" />
57
+ </fieldset>
58
+
59
+ <button type="submit">Submit</button>
60
+ <button type="button" @click="onReset">Reset</button>
61
+ </form>
37
62
  </template>
38
63
  ```
39
64
 
40
- ## Events
65
+ `USAddress` is presentational. Parent components own composable orchestration.
41
66
 
42
- - `valid-submit`: emits normalized `USAddressSubmitPayload`
43
- - `invalid-submit`: emits when submission fails validation
44
- - `reset`: emits after component reset
67
+ Required prop:
68
+ - `address`: pass the object returned by `useUSAddress(...)`.
45
69
 
46
- USAddress internally uses composables from `@featherk/composables/address`,
47
- including delayed fieldset validation orchestration via `useFieldsetValidationKit`.
70
+ Submission ownership:
71
+ - Parent form components own submission orchestration, reset behavior, and payload handling.
48
72
 
49
73
  ## Peer Dependencies
50
74
 
@@ -1,28 +1,61 @@
1
- import { type USAddressSubmitPayload } from "@featherk/composables/address";
1
+ interface USAddressViewModel {
2
+ formData: {
3
+ address1: string;
4
+ address2: string;
5
+ city: string;
6
+ state?: string;
7
+ zip: string;
8
+ };
9
+ addressValidate: {
10
+ value: boolean;
11
+ };
12
+ address1Valid: {
13
+ value: boolean;
14
+ };
15
+ cityValid: {
16
+ value: boolean;
17
+ };
18
+ stateValid: {
19
+ value: boolean;
20
+ };
21
+ zipValid: {
22
+ value: boolean;
23
+ };
24
+ addressValid: {
25
+ value: boolean;
26
+ };
27
+ stateDefaultItem: {
28
+ label: string;
29
+ value: string | null;
30
+ };
31
+ stateOptions: Array<{
32
+ label: string;
33
+ value: string;
34
+ }>;
35
+ zipDisplay: {
36
+ value: string;
37
+ };
38
+ zipWrapperClass: string;
39
+ onAddress1Input: (event: unknown) => void;
40
+ onAddress2Input: (event: unknown) => void;
41
+ onCityInput: (event: unknown) => void;
42
+ onStateChange: (event: unknown) => void;
43
+ onZipInput: (event: unknown) => void;
44
+ }
2
45
  type __VLS_Props = {
46
+ id: string;
47
+ address: USAddressViewModel;
3
48
  showAddress2?: boolean;
4
- submitLabel?: string;
5
- resetLabel?: string;
6
49
  address1Placeholder?: string;
7
50
  address2Placeholder?: string;
8
51
  cityPlaceholder?: string;
9
52
  zipPlaceholder?: string;
10
53
  };
11
- declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
- "valid-submit": (payload: USAddressSubmitPayload) => any;
13
- "invalid-submit": () => any;
14
- reset: () => any;
15
- }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
16
- "onValid-submit"?: (payload: USAddressSubmitPayload) => any;
17
- "onInvalid-submit"?: () => any;
18
- onReset?: () => any;
19
- }>, {
54
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
20
55
  showAddress2: boolean;
21
- submitLabel: string;
22
- resetLabel: string;
23
56
  address1Placeholder: string;
24
57
  address2Placeholder: string;
25
58
  cityPlaceholder: string;
26
59
  zipPlaceholder: string;
27
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
60
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
28
61
  export default _default;
@@ -1,196 +1,151 @@
1
1
  import "./ui.css";
2
- import { defineComponent as z, ref as I, openBlock as i, createElementBlock as k, withModifiers as g, createElementVNode as u, unref as e, createVNode as r, createBlock as o, withCtx as l, createTextVNode as d, createCommentVNode as b, toDisplayString as S } from "vue";
3
- import { Error as v, Hint as m } from "@progress/kendo-vue-labels";
4
- import { TextBox as y } from "@progress/kendo-vue-inputs";
5
- import { DropDownList as w } from "@progress/kendo-vue-dropdowns";
6
- import { Button as h } from "@progress/kendo-vue-buttons";
7
- import { useUSAddress as D } from "@featherk/composables/address";
8
- const A = { class: "address1" }, E = {
2
+ import { defineComponent as h, computed as V, openBlock as d, createElementBlock as I, createElementVNode as n, createVNode as v, unref as l, createBlock as u, withCtx as t, createTextVNode as s, createCommentVNode as c } from "vue";
3
+ import { Error as p, Hint as f } from "@progress/kendo-vue-labels";
4
+ import { TextBox as m } from "@progress/kendo-vue-inputs";
5
+ import { DropDownList as k } from "@progress/kendo-vue-dropdowns";
6
+ const P = { class: "fkui-us-address" }, z = { class: "address1" }, S = {
9
7
  key: 0,
10
8
  class: "address2"
11
- }, F = { class: "city" }, B = { class: "state" }, R = { class: "zip" }, L = { class: "messages" }, q = { class: "demo-actions" }, N = /* @__PURE__ */ z({
9
+ }, _ = { class: "city" }, C = { class: "state" }, g = { class: "zip" }, D = { class: "messages" }, w = /* @__PURE__ */ h({
12
10
  __name: "USAddress",
13
11
  props: {
12
+ id: {},
13
+ address: {},
14
14
  showAddress2: { type: Boolean, default: !0 },
15
- submitLabel: { default: "Submit" },
16
- resetLabel: { default: "Reset" },
17
15
  address1Placeholder: { default: "Enter street address" },
18
16
  address2Placeholder: { default: "Enter address line 2 (Optional)" },
19
17
  cityPlaceholder: { default: "Enter city" },
20
18
  zipPlaceholder: { default: "00000-0000" }
21
19
  },
22
- emits: ["valid-submit", "invalid-submit", "reset"],
23
- setup(c, { emit: V }) {
24
- const n = V, p = I(null), f = I(null), t = D({
25
- formRef: p,
26
- fieldsetRef: f
27
- }), C = async () => {
28
- const s = await t.submit();
29
- if (s.isValid) {
30
- n("valid-submit", s.payload);
31
- return;
32
- }
33
- n("invalid-submit");
34
- }, _ = () => {
35
- t.reset(), n("reset");
36
- };
37
- return (s, a) => (i(), k("form", {
38
- ref_key: "formRef",
39
- ref: p,
40
- novalidate: "",
41
- onSubmit: g(C, ["prevent"])
42
- }, [
43
- u("fieldset", {
44
- ref_key: "fieldsetRef",
45
- ref: f,
46
- class: "fkui-us-address",
47
- onFocusout: a[0] || (a[0] = //@ts-ignore
48
- (...P) => e(t).onFieldsetFocusout && e(t).onFieldsetFocusout(...P))
49
- }, [
50
- u("div", A, [
51
- r(e(y), {
52
- id: "address1",
53
- placeholder: s.address1Placeholder,
54
- value: e(t).formData.address1,
55
- valid: e(t).address1Valid.value,
56
- validate: e(t).addressValidate.value,
57
- validityStyles: e(t).addressValidate.value,
58
- onInput: e(t).onAddress1Input
59
- }, null, 8, ["placeholder", "value", "valid", "validate", "validityStyles", "onInput"]),
60
- e(t).address1Valid.value ? (i(), o(e(m), { key: 1 }, {
61
- default: l(() => [...a[2] || (a[2] = [
62
- d("Enter the street address including number.", -1)
63
- ])]),
64
- _: 1
65
- })) : (i(), o(e(v), { key: 0 }, {
66
- default: l(() => [...a[1] || (a[1] = [
67
- d("Address 1 is required.", -1)
68
- ])]),
69
- _: 1
70
- }))
71
- ]),
72
- s.showAddress2 ? (i(), k("div", E, [
73
- r(e(y), {
74
- id: "address2",
75
- placeholder: s.address2Placeholder,
76
- value: e(t).formData.address2,
77
- onInput: e(t).onAddress2Input
78
- }, null, 8, ["placeholder", "value", "onInput"]),
79
- r(e(m), null, {
80
- default: l(() => [...a[3] || (a[3] = [
81
- d("This field is optional.", -1)
82
- ])]),
83
- _: 1
84
- })
85
- ])) : b("", !0),
86
- u("div", F, [
87
- r(e(y), {
88
- id: "city",
89
- placeholder: s.cityPlaceholder,
90
- value: e(t).formData.city,
91
- valid: e(t).cityValid.value,
92
- validate: e(t).addressValidate.value,
93
- validityStyles: e(t).addressValidate.value,
94
- onInput: e(t).onCityInput
95
- }, null, 8, ["placeholder", "value", "valid", "validate", "validityStyles", "onInput"]),
96
- e(t).cityValid.value ? b("", !0) : (i(), o(e(v), { key: 0 }, {
97
- default: l(() => [...a[4] || (a[4] = [
98
- d("City is required.", -1)
99
- ])]),
100
- _: 1
101
- }))
102
- ]),
103
- u("div", B, [
104
- r(e(w), {
105
- id: "state",
106
- value: e(t).formData.state,
107
- onChange: e(t).onStateChange,
108
- defaultItem: e(t).stateDefaultItem,
109
- dataItems: e(t).stateOptions,
110
- textField: "label",
111
- valueField: "value",
112
- dataItemKey: "value",
113
- valuePrimitive: !0,
114
- valid: e(t).stateValid.value,
115
- validate: e(t).addressValidate.value,
116
- validityStyles: e(t).addressValidate.value
117
- }, null, 8, ["value", "onChange", "defaultItem", "dataItems", "valid", "validate", "validityStyles"]),
118
- e(t).stateValid.value ? b("", !0) : (i(), o(e(v), { key: 0 }, {
119
- default: l(() => [...a[5] || (a[5] = [
120
- d("State is required.", -1)
121
- ])]),
122
- _: 1
123
- }))
124
- ]),
125
- u("div", R, [
126
- r(e(y), {
127
- id: "zip",
128
- placeholder: s.zipPlaceholder,
129
- wrapperClass: e(t).zipWrapperClass,
130
- value: e(t).zipDisplay.value,
131
- onInput: e(t).onZipInput,
132
- valid: e(t).zipValid.value,
133
- validate: e(t).addressValidate.value,
134
- validityStyles: e(t).addressValidate.value,
135
- style: { width: "100%" }
136
- }, null, 8, ["placeholder", "wrapperClass", "value", "onInput", "valid", "validate", "validityStyles"]),
137
- e(t).zipValid.value ? (i(), o(e(m), { key: 1 }, {
138
- default: l(() => [...a[7] || (a[7] = [
139
- d("Please enter a 5 or 9 digit zip code.", -1)
140
- ])]),
141
- _: 1
142
- })) : (i(), o(e(v), { key: 0 }, {
143
- default: l(() => [...a[6] || (a[6] = [
144
- d("Please enter a 5 or 9 digit zip code.", -1)
145
- ])]),
146
- _: 1
147
- }))
148
- ]),
149
- u("div", L, [
150
- e(t).addressValid.value ? (i(), o(e(m), { key: 1 }, {
151
- default: l(() => [...a[9] || (a[9] = [
152
- d("Please enter address information.", -1)
153
- ])]),
154
- _: 1
155
- })) : (i(), o(e(v), { key: 0 }, {
156
- default: l(() => [...a[8] || (a[8] = [
157
- d(" Please fill out all required address fields. ", -1)
158
- ])]),
159
- _: 1
160
- }))
161
- ])
162
- ], 544),
163
- u("div", q, [
164
- r(e(h), {
165
- type: "submit",
166
- fillMode: "solid",
167
- themeColor: "primary"
168
- }, {
169
- default: l(() => [
170
- d(S(s.submitLabel), 1)
171
- ]),
20
+ setup(y) {
21
+ const i = y, r = V(() => ({
22
+ address1: `${i.id}-address1`,
23
+ address2: `${i.id}-address2`,
24
+ city: `${i.id}-city`,
25
+ state: `${i.id}-state`,
26
+ zip: `${i.id}-zip`
27
+ })), e = V(() => i.address);
28
+ return (o, a) => (d(), I("div", P, [
29
+ n("div", z, [
30
+ v(l(m), {
31
+ id: r.value.address1,
32
+ placeholder: o.address1Placeholder,
33
+ value: e.value.formData.address1,
34
+ valid: e.value.address1Valid.value,
35
+ validate: e.value.addressValidate.value,
36
+ validityStyles: e.value.addressValidate.value,
37
+ onInput: e.value.onAddress1Input
38
+ }, null, 8, ["id", "placeholder", "value", "valid", "validate", "validityStyles", "onInput"]),
39
+ e.value.address1Valid.value ? (d(), u(l(f), { key: 1 }, {
40
+ default: t(() => [...a[1] || (a[1] = [
41
+ s("Enter the street address including number.", -1)
42
+ ])]),
172
43
  _: 1
173
- }),
174
- r(e(h), {
175
- type: "button",
176
- fillMode: "outline",
177
- themeColor: "primary",
178
- onClick: _
179
- }, {
180
- default: l(() => [
181
- d(S(s.resetLabel), 1)
182
- ]),
44
+ })) : (d(), u(l(p), { key: 0 }, {
45
+ default: t(() => [...a[0] || (a[0] = [
46
+ s("Address 1 is required.", -1)
47
+ ])]),
48
+ _: 1
49
+ }))
50
+ ]),
51
+ o.showAddress2 ? (d(), I("div", S, [
52
+ v(l(m), {
53
+ id: r.value.address2,
54
+ placeholder: o.address2Placeholder,
55
+ value: e.value.formData.address2,
56
+ onInput: e.value.onAddress2Input
57
+ }, null, 8, ["id", "placeholder", "value", "onInput"]),
58
+ v(l(f), null, {
59
+ default: t(() => [...a[2] || (a[2] = [
60
+ s("This field is optional.", -1)
61
+ ])]),
183
62
  _: 1
184
63
  })
64
+ ])) : c("", !0),
65
+ n("div", _, [
66
+ v(l(m), {
67
+ id: r.value.city,
68
+ placeholder: o.cityPlaceholder,
69
+ value: e.value.formData.city,
70
+ valid: e.value.cityValid.value,
71
+ validate: e.value.addressValidate.value,
72
+ validityStyles: e.value.addressValidate.value,
73
+ onInput: e.value.onCityInput
74
+ }, null, 8, ["id", "placeholder", "value", "valid", "validate", "validityStyles", "onInput"]),
75
+ e.value.cityValid.value ? c("", !0) : (d(), u(l(p), { key: 0 }, {
76
+ default: t(() => [...a[3] || (a[3] = [
77
+ s("City is required.", -1)
78
+ ])]),
79
+ _: 1
80
+ }))
81
+ ]),
82
+ n("div", C, [
83
+ v(l(k), {
84
+ id: r.value.state,
85
+ value: e.value.formData.state,
86
+ onChange: e.value.onStateChange,
87
+ defaultItem: e.value.stateDefaultItem,
88
+ dataItems: e.value.stateOptions,
89
+ textField: "label",
90
+ valueField: "value",
91
+ dataItemKey: "value",
92
+ valuePrimitive: !0,
93
+ valid: e.value.stateValid.value,
94
+ validate: e.value.addressValidate.value,
95
+ validityStyles: e.value.addressValidate.value
96
+ }, null, 8, ["id", "value", "onChange", "defaultItem", "dataItems", "valid", "validate", "validityStyles"]),
97
+ e.value.stateValid.value ? c("", !0) : (d(), u(l(p), { key: 0 }, {
98
+ default: t(() => [...a[4] || (a[4] = [
99
+ s("State is required.", -1)
100
+ ])]),
101
+ _: 1
102
+ }))
103
+ ]),
104
+ n("div", g, [
105
+ v(l(m), {
106
+ id: r.value.zip,
107
+ placeholder: o.zipPlaceholder,
108
+ wrapperClass: e.value.zipWrapperClass,
109
+ value: e.value.zipDisplay.value,
110
+ onInput: e.value.onZipInput,
111
+ valid: e.value.zipValid.value,
112
+ validate: e.value.addressValidate.value,
113
+ validityStyles: e.value.addressValidate.value,
114
+ style: { width: "100%" }
115
+ }, null, 8, ["id", "placeholder", "wrapperClass", "value", "onInput", "valid", "validate", "validityStyles"]),
116
+ e.value.zipValid.value ? (d(), u(l(f), { key: 1 }, {
117
+ default: t(() => [...a[6] || (a[6] = [
118
+ s("Please enter a 5 or 9 digit zip code.", -1)
119
+ ])]),
120
+ _: 1
121
+ })) : (d(), u(l(p), { key: 0 }, {
122
+ default: t(() => [...a[5] || (a[5] = [
123
+ s("Please enter a 5 or 9 digit zip code.", -1)
124
+ ])]),
125
+ _: 1
126
+ }))
127
+ ]),
128
+ n("div", D, [
129
+ e.value.addressValid.value ? (d(), u(l(f), { key: 1 }, {
130
+ default: t(() => [...a[8] || (a[8] = [
131
+ s("Please enter address information.", -1)
132
+ ])]),
133
+ _: 1
134
+ })) : (d(), u(l(p), { key: 0 }, {
135
+ default: t(() => [...a[7] || (a[7] = [
136
+ s(" Please fill out all required address fields. ", -1)
137
+ ])]),
138
+ _: 1
139
+ }))
185
140
  ])
186
- ], 544));
141
+ ]));
187
142
  }
188
- }), M = (c, V) => {
189
- const n = c.__vccOpts || c;
190
- for (const [p, f] of V)
191
- n[p] = f;
192
- return n;
193
- }, W = /* @__PURE__ */ M(N, [["__scopeId", "data-v-14bc6580"]]);
143
+ }), A = (y, i) => {
144
+ const r = y.__vccOpts || y;
145
+ for (const [e, o] of i)
146
+ r[e] = o;
147
+ return r;
148
+ }, N = /* @__PURE__ */ A(w, [["__scopeId", "data-v-c5b6d2a5"]]);
194
149
  export {
195
- W as USAddress
150
+ N as USAddress
196
151
  };
@@ -1 +1 @@
1
- (function(d,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("@progress/kendo-vue-labels"),require("@progress/kendo-vue-inputs"),require("@progress/kendo-vue-dropdowns"),require("@progress/kendo-vue-buttons"),require("@featherk/composables/address")):typeof define=="function"&&define.amd?define(["exports","vue","@progress/kendo-vue-labels","@progress/kendo-vue-inputs","@progress/kendo-vue-dropdowns","@progress/kendo-vue-buttons","@featherk/composables/address"],e):(d=typeof globalThis<"u"?globalThis:d||self,e(d.FeatherKUi={},d.Vue,d.KendoVueLabels,d.KendoVueInputs,d.KendoVueDropdowns,d.KendoVueButtons,d.FeatherKComposablesAddress))})(this,(function(d,e,s,o,u,c,m){"use strict";const y={class:"address1"},V={key:0,class:"address2"},h={class:"city"},k={class:"state"},x={class:"zip"},N={class:"messages"},B={class:"demo-actions"},C=((f,p)=>{const l=f.__vccOpts||f;for(const[i,n]of p)l[i]=n;return l})(e.defineComponent({__name:"USAddress",props:{showAddress2:{type:Boolean,default:!0},submitLabel:{default:"Submit"},resetLabel:{default:"Reset"},address1Placeholder:{default:"Enter street address"},address2Placeholder:{default:"Enter address line 2 (Optional)"},cityPlaceholder:{default:"Enter city"},zipPlaceholder:{default:"00000-0000"}},emits:["valid-submit","invalid-submit","reset"],setup(f,{emit:p}){const l=p,i=e.ref(null),n=e.ref(null),t=m.useUSAddress({formRef:i,fieldsetRef:n}),w=async()=>{const a=await t.submit();if(a.isValid){l("valid-submit",a.payload);return}l("invalid-submit")},S=()=>{t.reset(),l("reset")};return(a,r)=>(e.openBlock(),e.createElementBlock("form",{ref_key:"formRef",ref:i,novalidate:"",onSubmit:e.withModifiers(w,["prevent"])},[e.createElementVNode("fieldset",{ref_key:"fieldsetRef",ref:n,class:"fkui-us-address",onFocusout:r[0]||(r[0]=(...T)=>e.unref(t).onFieldsetFocusout&&e.unref(t).onFieldsetFocusout(...T))},[e.createElementVNode("div",y,[e.createVNode(e.unref(o.TextBox),{id:"address1",placeholder:a.address1Placeholder,value:e.unref(t).formData.address1,valid:e.unref(t).address1Valid.value,validate:e.unref(t).addressValidate.value,validityStyles:e.unref(t).addressValidate.value,onInput:e.unref(t).onAddress1Input},null,8,["placeholder","value","valid","validate","validityStyles","onInput"]),e.unref(t).address1Valid.value?(e.openBlock(),e.createBlock(e.unref(s.Hint),{key:1},{default:e.withCtx(()=>[...r[2]||(r[2]=[e.createTextVNode("Enter the street address including number.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(s.Error),{key:0},{default:e.withCtx(()=>[...r[1]||(r[1]=[e.createTextVNode("Address 1 is required.",-1)])]),_:1}))]),a.showAddress2?(e.openBlock(),e.createElementBlock("div",V,[e.createVNode(e.unref(o.TextBox),{id:"address2",placeholder:a.address2Placeholder,value:e.unref(t).formData.address2,onInput:e.unref(t).onAddress2Input},null,8,["placeholder","value","onInput"]),e.createVNode(e.unref(s.Hint),null,{default:e.withCtx(()=>[...r[3]||(r[3]=[e.createTextVNode("This field is optional.",-1)])]),_:1})])):e.createCommentVNode("",!0),e.createElementVNode("div",h,[e.createVNode(e.unref(o.TextBox),{id:"city",placeholder:a.cityPlaceholder,value:e.unref(t).formData.city,valid:e.unref(t).cityValid.value,validate:e.unref(t).addressValidate.value,validityStyles:e.unref(t).addressValidate.value,onInput:e.unref(t).onCityInput},null,8,["placeholder","value","valid","validate","validityStyles","onInput"]),e.unref(t).cityValid.value?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(e.unref(s.Error),{key:0},{default:e.withCtx(()=>[...r[4]||(r[4]=[e.createTextVNode("City is required.",-1)])]),_:1}))]),e.createElementVNode("div",k,[e.createVNode(e.unref(u.DropDownList),{id:"state",value:e.unref(t).formData.state,onChange:e.unref(t).onStateChange,defaultItem:e.unref(t).stateDefaultItem,dataItems:e.unref(t).stateOptions,textField:"label",valueField:"value",dataItemKey:"value",valuePrimitive:!0,valid:e.unref(t).stateValid.value,validate:e.unref(t).addressValidate.value,validityStyles:e.unref(t).addressValidate.value},null,8,["value","onChange","defaultItem","dataItems","valid","validate","validityStyles"]),e.unref(t).stateValid.value?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(e.unref(s.Error),{key:0},{default:e.withCtx(()=>[...r[5]||(r[5]=[e.createTextVNode("State is required.",-1)])]),_:1}))]),e.createElementVNode("div",x,[e.createVNode(e.unref(o.TextBox),{id:"zip",placeholder:a.zipPlaceholder,wrapperClass:e.unref(t).zipWrapperClass,value:e.unref(t).zipDisplay.value,onInput:e.unref(t).onZipInput,valid:e.unref(t).zipValid.value,validate:e.unref(t).addressValidate.value,validityStyles:e.unref(t).addressValidate.value,style:{width:"100%"}},null,8,["placeholder","wrapperClass","value","onInput","valid","validate","validityStyles"]),e.unref(t).zipValid.value?(e.openBlock(),e.createBlock(e.unref(s.Hint),{key:1},{default:e.withCtx(()=>[...r[7]||(r[7]=[e.createTextVNode("Please enter a 5 or 9 digit zip code.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(s.Error),{key:0},{default:e.withCtx(()=>[...r[6]||(r[6]=[e.createTextVNode("Please enter a 5 or 9 digit zip code.",-1)])]),_:1}))]),e.createElementVNode("div",N,[e.unref(t).addressValid.value?(e.openBlock(),e.createBlock(e.unref(s.Hint),{key:1},{default:e.withCtx(()=>[...r[9]||(r[9]=[e.createTextVNode("Please enter address information.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(s.Error),{key:0},{default:e.withCtx(()=>[...r[8]||(r[8]=[e.createTextVNode(" Please fill out all required address fields. ",-1)])]),_:1}))])],544),e.createElementVNode("div",B,[e.createVNode(e.unref(c.Button),{type:"submit",fillMode:"solid",themeColor:"primary"},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(a.submitLabel),1)]),_:1}),e.createVNode(e.unref(c.Button),{type:"button",fillMode:"outline",themeColor:"primary",onClick:S},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(a.resetLabel),1)]),_:1})])],544))}}),[["__scopeId","data-v-14bc6580"]]);d.USAddress=C,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(d,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("@progress/kendo-vue-labels"),require("@progress/kendo-vue-inputs"),require("@progress/kendo-vue-dropdowns")):typeof define=="function"&&define.amd?define(["exports","vue","@progress/kendo-vue-labels","@progress/kendo-vue-inputs","@progress/kendo-vue-dropdowns"],e):(d=typeof globalThis<"u"?globalThis:d||self,e(d.FeatherKUi={},d.Vue,d.KendoVueLabels,d.KendoVueInputs,d.KendoVueDropdowns))})(this,(function(d,e,l,o,p){"use strict";const u={class:"fkui-us-address"},c={class:"address1"},f={key:0,class:"address2"},y={class:"city"},v={class:"state"},m={class:"zip"},V={class:"messages"},h=((n,r)=>{const s=n.__vccOpts||n;for(const[t,i]of r)s[t]=i;return s})(e.defineComponent({__name:"USAddress",props:{id:{},address:{},showAddress2:{type:Boolean,default:!0},address1Placeholder:{default:"Enter street address"},address2Placeholder:{default:"Enter address line 2 (Optional)"},cityPlaceholder:{default:"Enter city"},zipPlaceholder:{default:"00000-0000"}},setup(n){const r=n,s=e.computed(()=>({address1:`${r.id}-address1`,address2:`${r.id}-address2`,city:`${r.id}-city`,state:`${r.id}-state`,zip:`${r.id}-zip`})),t=e.computed(()=>r.address);return(i,a)=>(e.openBlock(),e.createElementBlock("div",u,[e.createElementVNode("div",c,[e.createVNode(e.unref(o.TextBox),{id:s.value.address1,placeholder:i.address1Placeholder,value:t.value.formData.address1,valid:t.value.address1Valid.value,validate:t.value.addressValidate.value,validityStyles:t.value.addressValidate.value,onInput:t.value.onAddress1Input},null,8,["id","placeholder","value","valid","validate","validityStyles","onInput"]),t.value.address1Valid.value?(e.openBlock(),e.createBlock(e.unref(l.Hint),{key:1},{default:e.withCtx(()=>[...a[1]||(a[1]=[e.createTextVNode("Enter the street address including number.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(l.Error),{key:0},{default:e.withCtx(()=>[...a[0]||(a[0]=[e.createTextVNode("Address 1 is required.",-1)])]),_:1}))]),i.showAddress2?(e.openBlock(),e.createElementBlock("div",f,[e.createVNode(e.unref(o.TextBox),{id:s.value.address2,placeholder:i.address2Placeholder,value:t.value.formData.address2,onInput:t.value.onAddress2Input},null,8,["id","placeholder","value","onInput"]),e.createVNode(e.unref(l.Hint),null,{default:e.withCtx(()=>[...a[2]||(a[2]=[e.createTextVNode("This field is optional.",-1)])]),_:1})])):e.createCommentVNode("",!0),e.createElementVNode("div",y,[e.createVNode(e.unref(o.TextBox),{id:s.value.city,placeholder:i.cityPlaceholder,value:t.value.formData.city,valid:t.value.cityValid.value,validate:t.value.addressValidate.value,validityStyles:t.value.addressValidate.value,onInput:t.value.onCityInput},null,8,["id","placeholder","value","valid","validate","validityStyles","onInput"]),t.value.cityValid.value?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(e.unref(l.Error),{key:0},{default:e.withCtx(()=>[...a[3]||(a[3]=[e.createTextVNode("City is required.",-1)])]),_:1}))]),e.createElementVNode("div",v,[e.createVNode(e.unref(p.DropDownList),{id:s.value.state,value:t.value.formData.state,onChange:t.value.onStateChange,defaultItem:t.value.stateDefaultItem,dataItems:t.value.stateOptions,textField:"label",valueField:"value",dataItemKey:"value",valuePrimitive:!0,valid:t.value.stateValid.value,validate:t.value.addressValidate.value,validityStyles:t.value.addressValidate.value},null,8,["id","value","onChange","defaultItem","dataItems","valid","validate","validityStyles"]),t.value.stateValid.value?e.createCommentVNode("",!0):(e.openBlock(),e.createBlock(e.unref(l.Error),{key:0},{default:e.withCtx(()=>[...a[4]||(a[4]=[e.createTextVNode("State is required.",-1)])]),_:1}))]),e.createElementVNode("div",m,[e.createVNode(e.unref(o.TextBox),{id:s.value.zip,placeholder:i.zipPlaceholder,wrapperClass:t.value.zipWrapperClass,value:t.value.zipDisplay.value,onInput:t.value.onZipInput,valid:t.value.zipValid.value,validate:t.value.addressValidate.value,validityStyles:t.value.addressValidate.value,style:{width:"100%"}},null,8,["id","placeholder","wrapperClass","value","onInput","valid","validate","validityStyles"]),t.value.zipValid.value?(e.openBlock(),e.createBlock(e.unref(l.Hint),{key:1},{default:e.withCtx(()=>[...a[6]||(a[6]=[e.createTextVNode("Please enter a 5 or 9 digit zip code.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(l.Error),{key:0},{default:e.withCtx(()=>[...a[5]||(a[5]=[e.createTextVNode("Please enter a 5 or 9 digit zip code.",-1)])]),_:1}))]),e.createElementVNode("div",V,[t.value.addressValid.value?(e.openBlock(),e.createBlock(e.unref(l.Hint),{key:1},{default:e.withCtx(()=>[...a[8]||(a[8]=[e.createTextVNode("Please enter address information.",-1)])]),_:1})):(e.openBlock(),e.createBlock(e.unref(l.Error),{key:0},{default:e.withCtx(()=>[...a[7]||(a[7]=[e.createTextVNode(" Please fill out all required address fields. ",-1)])]),_:1}))])]))}}),[["__scopeId","data-v-c5b6d2a5"]]);d.USAddress=h,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
package/dist/ui.css CHANGED
@@ -1 +1 @@
1
- .fkui-us-address[data-v-14bc6580]{display:grid;grid-template-columns:2fr 1fr 1fr;grid-template-areas:"address1 address1 address1" "address2 address2 address2" "city state zip" "messages messages messages";gap:.75rem;padding:1rem}.fkui-us-address .address1[data-v-14bc6580]{grid-area:address1}.fkui-us-address .address2[data-v-14bc6580]{grid-area:address2}.fkui-us-address .city[data-v-14bc6580]{grid-area:city}.fkui-us-address .state[data-v-14bc6580]{grid-area:state}.fkui-us-address .zip[data-v-14bc6580]{grid-area:zip}.fkui-us-address .messages[data-v-14bc6580]{grid-area:messages}.fkui-us-address [data-v-14bc6580] .fk-zip-textbox{min-width:100%}[data-v-14bc6580] .demo-actions{min-height:4rem;align-items:center;flex:1 0 2rem}
1
+ .fkui-us-address[data-v-c5b6d2a5]{display:grid;grid-template-columns:2fr 1fr 1fr;grid-template-areas:"address1 address1 address1" "address2 address2 address2" "city state zip" "messages messages messages";gap:.75rem;padding:1rem}.fkui-us-address .address1[data-v-c5b6d2a5]{grid-area:address1}.fkui-us-address .address2[data-v-c5b6d2a5]{grid-area:address2}.fkui-us-address .city[data-v-c5b6d2a5]{grid-area:city}.fkui-us-address .state[data-v-c5b6d2a5]{grid-area:state}.fkui-us-address .zip[data-v-c5b6d2a5]{grid-area:zip}.fkui-us-address .messages[data-v-c5b6d2a5]{grid-area:messages}.fkui-us-address [data-v-c5b6d2a5] .fk-zip-textbox{min-width:100%}
@@ -1,68 +1,87 @@
1
1
  # `USAddress`
2
2
 
3
- `USAddress` is a ready-to-use US address form component for Vue 3 and Kendo UI for Vue.
3
+ `USAddress` is a presentational US address form renderer for Vue 3 and Kendo UI for Vue.
4
4
 
5
- It is powered by `useUSAddress` from `@featherk/composables/address` and includes:
5
+ It renders fields and validation messages from a parent-owned `useUSAddress` view model.
6
+
7
+ The component includes:
6
8
 
7
9
  - Required field validation for Address 1, City, State, and ZIP
8
10
  - Optional Address 2
9
11
  - ZIP formatting/validation behavior via `useZipTextBox`
10
- - Delayed fieldset validation orchestration via `useFieldsetValidationKit`
11
- - Normalized submit payloads via `valid-submit`
12
+ - Deterministic rendering based on parent-provided state and handlers
12
13
 
13
14
  ## Import
14
15
 
15
16
  ```ts
16
17
  import { USAddress } from "@featherk/ui";
17
- import type { USAddressSubmitPayload } from "@featherk/composables/address";
18
+ import { useUSAddress } from "@featherk/composables/address";
18
19
  ```
19
20
 
20
21
  ## Basic Usage
21
22
 
22
23
  ```vue
23
24
  <script setup lang="ts">
25
+ import { ref, toRef } from "vue";
24
26
  import { USAddress } from "@featherk/ui";
25
- import type { USAddressSubmitPayload } from "@featherk/composables/address";
27
+ import { useUSAddress } from "@featherk/composables/address";
26
28
 
27
- function onValidSubmit(payload: USAddressSubmitPayload) {
28
- console.log("valid", payload);
29
- }
29
+ const shippingFieldsetRef = ref<HTMLFieldSetElement | null>(null);
30
30
 
31
- function onInvalidSubmit() {
32
- console.log("invalid");
33
- }
31
+ const shippingAddress = useUSAddress({
32
+ fieldsetRef: toRef(() => shippingFieldsetRef.value),
33
+ addressId: "shipping-address",
34
+ });
35
+
36
+ const onFieldsetFocusout = (event: FocusEvent) => {
37
+ shippingAddress.onFieldsetFocusout(event);
38
+ };
34
39
 
35
40
  function onReset() {
36
- console.log("reset");
41
+ shippingAddress.reset();
42
+ }
43
+
44
+ async function onSubmit() {
45
+ const result = await shippingAddress.validateForSubmit();
46
+ if (!result.isValid) return;
47
+
48
+ console.log(result.payload);
37
49
  }
38
50
  </script>
39
51
 
40
52
  <template>
41
- <USAddress
42
- @valid-submit="onValidSubmit"
43
- @invalid-submit="onInvalidSubmit"
44
- @reset="onReset"
45
- />
53
+ <form @submit.prevent="onSubmit">
54
+ <fieldset ref="shippingFieldsetRef" @focusout="onFieldsetFocusout">
55
+ <USAddress id="shipping-address" :address="shippingAddress" />
56
+
57
+ <label for="shipping-attn">Attention</label>
58
+ <input id="shipping-attn" type="text" />
59
+ </fieldset>
60
+
61
+ <button type="submit">Submit</button>
62
+ <button type="button" @click="onReset">Reset</button>
63
+ </form>
46
64
  </template>
47
65
  ```
48
66
 
49
67
  ## Props
50
68
 
69
+ - `id: string` (required) - Used to generate unique field ids (`${id}-address1`, `${id}-zip`, etc.) for multi-instance pages
70
+ - `address` (required) - Result object returned by `useUSAddress(...)` from `@featherk/composables/address`
51
71
  - `showAddress2?: boolean` (default: `true`)
52
- - `submitLabel?: string` (default: `"Submit"`)
53
- - `resetLabel?: string` (default: `"Reset"`)
54
72
  - `address1Placeholder?: string` (default: `"Enter street address"`)
55
73
  - `address2Placeholder?: string` (default: `"Enter address line 2 (Optional)"`)
56
74
  - `cityPlaceholder?: string` (default: `"Enter city"`)
57
75
  - `zipPlaceholder?: string` (default: `"00000-0000"`)
58
76
 
59
- ## Events
77
+ ## Submission Ownership
60
78
 
61
- - `valid-submit`: emits `USAddressSubmitPayload` when submit succeeds
62
- - `invalid-submit`: emits when submit fails validation
63
- - `reset`: emits after reset action
79
+ - `USAddress` intentionally does not expose submit or reset methods.
80
+ - Parent form components own submission and reset flow through the composable.
64
81
 
65
82
  ## Notes
66
83
 
67
84
  - Component styles are delivered automatically from the package ESM entry.
68
85
  - `USAddress` uses Kendo UI inputs/buttons/labels/dropdowns as peer dependencies of `@featherk/ui`.
86
+ - `USAddress` does not render an internal `<form>` element, so it can be safely embedded inside a parent form.
87
+ - `USAddress` does not render an internal `<fieldset>`. Wrap it in your own `<fieldset>` and wire `onFieldsetFocusout` at the parent level.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/ui",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "main": "dist/featherk-ui.umd.js",
5
5
  "module": "dist/featherk-ui.es.js",
6
6
  "types": "dist/index.d.ts",