@viveksinghind/narrative-form-vue 1.0.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/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/composables/useFormState.d.ts +15 -0
- package/dist/composables/useFormState.d.ts.map +1 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +319 -0
- package/dist/style.css +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vivek Singh
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @viveksinghind/narrative-form-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 implementation for Narrative Form, a dynamic typewriter-style sign-up flow.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @viveksinghind/narrative-form-vue @viveksinghind/narrative-form-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```vue
|
|
14
|
+
<script setup>
|
|
15
|
+
import { NarrativeForm } from '@viveksinghind/narrative-form-vue';
|
|
16
|
+
|
|
17
|
+
const myFormConfig = {
|
|
18
|
+
form: { id: "test", name: "Test Form", version: 1 },
|
|
19
|
+
welcome: {
|
|
20
|
+
heading: "Welcome to the future",
|
|
21
|
+
subtext: "Let's get started",
|
|
22
|
+
ctaLabel: "Begin"
|
|
23
|
+
},
|
|
24
|
+
fields: [
|
|
25
|
+
{
|
|
26
|
+
key: "name",
|
|
27
|
+
type: "text",
|
|
28
|
+
prefix: "Hi, my name is",
|
|
29
|
+
validation: { required: true }
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
done: {
|
|
33
|
+
message: "All done! Thank you.",
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleComplete = (values) => {
|
|
38
|
+
console.log('Form Complete!', values);
|
|
39
|
+
};
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<NarrativeForm
|
|
44
|
+
:config="myFormConfig"
|
|
45
|
+
@complete="handleComplete"
|
|
46
|
+
/>
|
|
47
|
+
</template>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
- Built for Vue 3 Composition API
|
|
52
|
+
- Uses lightweight `shallowRef` reactivity bridge
|
|
53
|
+
- Smooth CSS animations
|
|
54
|
+
- Themeable via config
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NarrativeField, FormStateSnapshot, NarrativeFieldValues, NarrativeMeta } from '@viveksinghind/narrative-form-core';
|
|
2
|
+
export declare function useFormState(fields: readonly NarrativeField[]): {
|
|
3
|
+
snapshot: import('vue').ShallowRef<FormStateSnapshot, FormStateSnapshot>;
|
|
4
|
+
startTyping: (key: string) => void;
|
|
5
|
+
activateField: (key: string) => void;
|
|
6
|
+
confirmField: (key: string, value: string | string[]) => void;
|
|
7
|
+
editField: (key: string) => void;
|
|
8
|
+
reconfirmField: (key: string, value: string | string[]) => void;
|
|
9
|
+
next: () => void;
|
|
10
|
+
focusField: (key: string) => void;
|
|
11
|
+
reset: () => void;
|
|
12
|
+
getValues: () => NarrativeFieldValues;
|
|
13
|
+
getMeta: (formId?: string, formVersion?: number) => NarrativeMeta;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=useFormState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFormState.d.ts","sourceRoot":"","sources":["../../src/composables/useFormState.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGjI,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE;;uBAcvC,MAAM;yBACJ,MAAM;wBACP,MAAM,SAAS,MAAM,GAAG,MAAM,EAAE;qBACnC,MAAM;0BACD,MAAM,SAAS,MAAM,GAAG,MAAM,EAAE;;sBAEpC,MAAM;;;uBAML,MAAM,gBAAgB,MAAM;EAElD"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),C=require("@viveksinghind/narrative-form-core");function I(l){const t=new C.FormStateEngine(l,()=>{n.value=t.getSnapshot(),e.triggerRef(n)}),n=e.shallowRef(t.getSnapshot());return e.onUnmounted(()=>{t.reset()}),{snapshot:n,startTyping:o=>t.startTyping(o),activateField:o=>t.activateField(o),confirmField:(o,u)=>t.confirmField(o,u),editField:o=>t.editField(o),reconfirmField:(o,u)=>t.reconfirmField(o,u),next:()=>t.next(),focusField:o=>t.focusField(o),reset:()=>{t.reset(),n.value=t.getSnapshot()},getValues:()=>t.getValues(),getMeta:(o,u)=>t.getMeta(o,u)}}const R={class:"ns-welcome fade-in"},W={class:"ns-welcome-title"},K={key:0,class:"ns-welcome-subtitle"},U=e.defineComponent({__name:"WelcomeScreen",props:{welcome:{},typewriter:{}},emits:["start"],setup(l,{emit:t}){const n=l,o=t,u=e.ref("");let p;return e.onMounted(()=>{if(!n.typewriter.enabled){u.value=n.welcome.heading||"";return}let m=0;p=setInterval(()=>{u.value=(n.welcome.heading||"").slice(0,m+1),m++,m>=(n.welcome.heading||"").length&&clearInterval(p)},n.typewriter.speed??30)}),e.onUnmounted(()=>{clearInterval(p)}),(m,s)=>(e.openBlock(),e.createElementBlock("div",R,[e.createElementVNode("h1",W,e.toDisplayString(u.value),1),l.welcome.subtext?(e.openBlock(),e.createElementBlock("p",K,e.toDisplayString(l.welcome.subtext),1)):e.createCommentVNode("",!0),e.createElementVNode("button",{type:"button",class:"ns-welcome-start",onClick:s[0]||(s[0]=g=>o("start"))},e.toDisplayString(l.welcome.ctaLabel||"Start"),1)]))}}),B=(l,t)=>{const n=l.__vccOpts||l;for(const[o,u]of t)n[o]=u;return n},D=B(U,[["__scopeId","data-v-dbeb46ea"]]),q={class:"ns-done fade-in"},A={class:"ns-done-title"},O=e.defineComponent({__name:"DoneScreen",props:{done:{},values:{},meta:{},typewriter:{}},setup(l){return(t,n)=>(e.openBlock(),e.createElementBlock("div",q,[e.createElementVNode("h1",A,e.toDisplayString(typeof l.done.message=="function"?l.done.message(l.values):l.done.message),1)]))}}),M=B(O,[["__scopeId","data-v-7802c8f5"]]),P={key:0,class:"ns-error-wrapper"},j={class:"ns-error-text"},H=e.defineComponent({__name:"ErrorMessage",props:{message:{},config:{}},setup(l){return(t,n)=>(e.openBlock(),e.createBlock(e.Transition,{name:"fade-slide"},{default:e.withCtx(()=>[l.message?(e.openBlock(),e.createElementBlock("div",P,[e.createElementVNode("p",j,e.toDisplayString(l.message),1)])):e.createCommentVNode("",!0)]),_:1}))}}),_=B(H,[["__scopeId","data-v-a8be2d7b"]]),z={class:"ns-line fade-in"},G={class:"ns-prompt-row"},J={class:"ns-prompt"},Q={key:0,class:"ns-value"},X={key:0,class:"ns-input-row"},Y=["type","disabled","value","onKeydown"],Z=e.defineComponent({__name:"Line",props:{field:{},status:{},value:{},allValues:{},typewriter:{},editable:{type:Boolean},locked:{type:Boolean},editLabel:{}},emits:["typingComplete","confirm","edit","error","change","focus","blur"],setup(l,{emit:t}){const n=l,o=t,u=e.ref(null),p=e.ref(""),m=e.ref(n.value?String(n.value):""),s=e.ref(null),g=e.ref(!1);let w;e.watch(()=>n.value,f=>{f!==void 0&&String(f)!==m.value&&(m.value=String(f))}),e.watch(()=>n.status,f=>{if(f==="typing"){if(!n.typewriter.enabled){p.value=n.field.prompt,o("typingComplete",n.field.key);return}let i=0;w=setInterval(()=>{p.value=n.field.prompt.slice(0,i+1),i++,i>=n.field.prompt.length&&(clearInterval(w),o("typingComplete",n.field.key))},n.typewriter.speed??30)}else(f==="active"||f==="editing")&&e.nextTick(()=>{var i;(i=u.value)==null||i.focus()})},{immediate:!0}),e.onUnmounted(()=>{clearInterval(w)});const E=f=>{const i=f.target;m.value=i.value,o("change",n.field.key,i.value),s.value&&(s.value=null)},V=async()=>{const f=C.validateField(n.field,m.value);if(!f.valid){s.value=f.error||"Invalid input",o("error",n.field.key,s.value);return}if(C.hasAsyncValidation(n.field)){g.value=!0;const i=await C.validateFieldAsync(n.field,m.value);if(g.value=!1,!i.valid){s.value=i.error||"Invalid input",o("error",n.field.key,s.value);return}}s.value=null,o("confirm",n.field.key,m.value)};return(f,i)=>(e.openBlock(),e.createElementBlock("div",z,[e.createElementVNode("div",G,[e.createElementVNode("span",J,e.toDisplayString(l.status==="typing"?p.value:l.field.prompt),1),l.status==="confirmed"?(e.openBlock(),e.createElementBlock("span",Q,e.toDisplayString(m.value),1)):e.createCommentVNode("",!0),l.status==="confirmed"&&l.editable&&!l.locked?(e.openBlock(),e.createElementBlock("button",{key:1,type:"button",class:"ns-edit-btn",onClick:i[0]||(i[0]=F=>o("edit",l.field.key))},e.toDisplayString(l.editLabel),1)):e.createCommentVNode("",!0)]),l.status==="active"||l.status==="editing"?(e.openBlock(),e.createElementBlock("div",X,[e.createElementVNode("input",{ref_key:"inputRef",ref:u,type:l.field.type==="email"?"email":l.field.type==="password"?"password":"text",class:"ns-input",disabled:g.value,value:m.value,onInput:E,onFocus:i[1]||(i[1]=F=>o("focus",l.field.key)),onBlur:i[2]||(i[2]=F=>o("blur",l.field.key,m.value)),onKeydown:e.withKeys(e.withModifiers(V,["prevent"]),["enter"])},null,40,Y)])):e.createCommentVNode("",!0),e.createVNode(_,{message:s.value},null,8,["message"])]))}}),L=B(Z,[["__scopeId","data-v-d4b482a6"]]),ee={key:1,class:"ns-form-body"},te=e.defineComponent({__name:"NarrativeForm",props:{fields:{},formConfig:{},typewriter:{},welcome:{},done:{},editable:{type:Boolean,default:!0},editLabel:{},callbacks:{},defaultValues:{},values:{},strings:{},reducedMotion:{type:Boolean}},setup(l){const t=l,n=e.computed(()=>t.fields?t.fields:t.formConfig?t.formConfig.fields:[]),o=e.computed(()=>{if(t.welcome)return t.welcome;if(t.formConfig&&"welcome"in t.formConfig)return t.formConfig.welcome}),u=e.computed(()=>{if(t.done)return t.done;if(t.formConfig&&"done"in t.formConfig)return t.formConfig.done}),p=e.computed(()=>{var d;return{...t.typewriter,enabled:t.reducedMotion?!1:((d=t.typewriter)==null?void 0:d.enabled)??!0}}),m=e.computed(()=>C.mergeStrings(t.strings)),{snapshot:s,startTyping:g,activateField:w,confirmField:E,editField:V,reconfirmField:f,getMeta:i}=I(n.value),F=e.computed(()=>{var d;return((d=o.value)==null?void 0:d.show)!==!1&&o.value!==void 0}),b=e.ref(!F.value),N=e.ref(!1),x=()=>{if(!b.value||N.value||n.value.length===0)return;N.value=!0;const d=n.value.find(r=>s.value.statuses[r.key]!=="confirmed");d&&g(d.key)};e.watch(b,x),e.onMounted(x);const T=(d,r)=>{var v;s.value.statuses[d]==="editing"?f(d,r):E(d,r),(v=t.callbacks)!=null&&v.onFieldComplete&&t.callbacks.onFieldComplete(d,r,0);const S=n.value.findIndex(c=>c.key===d);for(let c=S+1;c<n.value.length;c++){const a=n.value[c];if(!(!a||s.value.statuses[a.key]==="confirmed")){g(a.key);break}}},$=e.computed(()=>n.value.filter(d=>{const r=s.value.statuses[d.key];return r==="typing"||r==="active"||r==="confirmed"||r==="editing"})),h=e.ref(null);return e.watch(()=>$.value.length,()=>{e.nextTick(()=>{h.value&&(h.value.scrollTop=h.value.scrollHeight)})}),(d,r)=>(e.openBlock(),e.createElementBlock("div",{class:"ns-root",ref_key:"scrollContainerRef",ref:h},[!b.value&&o.value?(e.openBlock(),e.createBlock(D,{key:0,welcome:o.value,typewriter:p.value,onStart:r[0]||(r[0]=k=>b.value=!0)},null,8,["welcome","typewriter"])):e.createCommentVNode("",!0),b.value?(e.openBlock(),e.createElementBlock("div",ee,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList($.value,k=>{var S;return e.openBlock(),e.createBlock(L,{key:k.key,field:k,status:e.unref(s).statuses[k.key]??"idle",value:((S=t.values)==null?void 0:S[k.key])??e.unref(s).values[k.key],allValues:e.unref(s).values,typewriter:p.value,editable:t.editable,locked:!1,editLabel:t.editLabel??m.value.editLabel,onTypingComplete:e.unref(w),onConfirm:T,onEdit:r[1]||(r[1]=v=>{var c,a;e.unref(V)(v),(a=(c=t.callbacks)==null?void 0:c.onEdit)==null||a.call(c,v)}),onError:r[2]||(r[2]=(v,c)=>{var a,y;return(y=(a=t.callbacks)==null?void 0:a.onError)==null?void 0:y.call(a,v,c)}),onChange:r[3]||(r[3]=(v,c)=>{var a,y;return(y=(a=t.callbacks)==null?void 0:a.onChange)==null?void 0:y.call(a,v,c)}),onFocus:r[4]||(r[4]=v=>{var c,a;return(a=(c=t.callbacks)==null?void 0:c.onFieldFocus)==null?void 0:a.call(c,v)}),onBlur:r[5]||(r[5]=(v,c)=>{var a,y;return(y=(a=t.callbacks)==null?void 0:a.onFieldBlur)==null?void 0:y.call(a,v,c)})},null,8,["field","status","value","allValues","typewriter","editable","editLabel","onTypingComplete"])}),128))])):e.createCommentVNode("",!0),e.unref(s).isComplete&&u.value?(e.openBlock(),e.createBlock(M,{key:2,done:u.value,values:e.unref(s).values,meta:e.unref(i)(),typewriter:p.value},null,8,["done","values","meta","typewriter"])):e.createCommentVNode("",!0)],512))}}),ne=B(te,[["__scopeId","data-v-33050240"]]);exports.DoneScreen=M;exports.ErrorMessage=_;exports.Line=L;exports.NarrativeForm=ne;exports.WelcomeScreen=D;exports.useFormState=I;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as NarrativeForm } from './components/NarrativeForm.vue';
|
|
2
|
+
export { default as WelcomeScreen } from './components/WelcomeScreen.vue';
|
|
3
|
+
export { default as DoneScreen } from './components/DoneScreen.vue';
|
|
4
|
+
export { default as Line } from './components/Line.vue';
|
|
5
|
+
export { default as ErrorMessage } from './components/ErrorMessage.vue';
|
|
6
|
+
export * from './composables/useFormState';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACxE,cAAc,4BAA4B,CAAC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { triggerRef as U, shallowRef as j, onUnmounted as N, defineComponent as _, ref as g, onMounted as H, openBlock as f, createElementBlock as y, createElementVNode as $, toDisplayString as k, createCommentVNode as F, createBlock as V, Transition as q, withCtx as z, watch as E, nextTick as O, withKeys as G, withModifiers as J, createVNode as Q, computed as x, Fragment as X, renderList as Y, unref as b } from "vue";
|
|
2
|
+
import { FormStateEngine as Z, validateField as ee, hasAsyncValidation as te, validateFieldAsync as ne, mergeStrings as le } from "@viveksinghind/narrative-form-core";
|
|
3
|
+
function se(n) {
|
|
4
|
+
const e = new Z(n, () => {
|
|
5
|
+
t.value = e.getSnapshot(), U(t);
|
|
6
|
+
}), t = j(e.getSnapshot());
|
|
7
|
+
return N(() => {
|
|
8
|
+
e.reset();
|
|
9
|
+
}), {
|
|
10
|
+
snapshot: t,
|
|
11
|
+
startTyping: (l) => e.startTyping(l),
|
|
12
|
+
activateField: (l) => e.activateField(l),
|
|
13
|
+
confirmField: (l, u) => e.confirmField(l, u),
|
|
14
|
+
editField: (l) => e.editField(l),
|
|
15
|
+
reconfirmField: (l, u) => e.reconfirmField(l, u),
|
|
16
|
+
next: () => e.next(),
|
|
17
|
+
focusField: (l) => e.focusField(l),
|
|
18
|
+
reset: () => {
|
|
19
|
+
e.reset(), t.value = e.getSnapshot();
|
|
20
|
+
},
|
|
21
|
+
getValues: () => e.getValues(),
|
|
22
|
+
getMeta: (l, u) => e.getMeta(l, u)
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const ae = { class: "ns-welcome fade-in" }, ie = { class: "ns-welcome-title" }, oe = {
|
|
26
|
+
key: 0,
|
|
27
|
+
class: "ns-welcome-subtitle"
|
|
28
|
+
}, re = /* @__PURE__ */ _({
|
|
29
|
+
__name: "WelcomeScreen",
|
|
30
|
+
props: {
|
|
31
|
+
welcome: {},
|
|
32
|
+
typewriter: {}
|
|
33
|
+
},
|
|
34
|
+
emits: ["start"],
|
|
35
|
+
setup(n, { emit: e }) {
|
|
36
|
+
const t = n, l = e, u = g("");
|
|
37
|
+
let p;
|
|
38
|
+
return H(() => {
|
|
39
|
+
if (!t.typewriter.enabled) {
|
|
40
|
+
u.value = t.welcome.heading || "";
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let c = 0;
|
|
44
|
+
p = setInterval(() => {
|
|
45
|
+
u.value = (t.welcome.heading || "").slice(0, c + 1), c++, c >= (t.welcome.heading || "").length && clearInterval(p);
|
|
46
|
+
}, t.typewriter.speed ?? 30);
|
|
47
|
+
}), N(() => {
|
|
48
|
+
clearInterval(p);
|
|
49
|
+
}), (c, i) => (f(), y("div", ae, [
|
|
50
|
+
$("h1", ie, k(u.value), 1),
|
|
51
|
+
n.welcome.subtext ? (f(), y("p", oe, k(n.welcome.subtext), 1)) : F("", !0),
|
|
52
|
+
$("button", {
|
|
53
|
+
type: "button",
|
|
54
|
+
class: "ns-welcome-start",
|
|
55
|
+
onClick: i[0] || (i[0] = (h) => l("start"))
|
|
56
|
+
}, k(n.welcome.ctaLabel || "Start"), 1)
|
|
57
|
+
]));
|
|
58
|
+
}
|
|
59
|
+
}), L = (n, e) => {
|
|
60
|
+
const t = n.__vccOpts || n;
|
|
61
|
+
for (const [l, u] of e)
|
|
62
|
+
t[l] = u;
|
|
63
|
+
return t;
|
|
64
|
+
}, ue = /* @__PURE__ */ L(re, [["__scopeId", "data-v-dbeb46ea"]]), de = { class: "ns-done fade-in" }, ce = { class: "ns-done-title" }, ve = /* @__PURE__ */ _({
|
|
65
|
+
__name: "DoneScreen",
|
|
66
|
+
props: {
|
|
67
|
+
done: {},
|
|
68
|
+
values: {},
|
|
69
|
+
meta: {},
|
|
70
|
+
typewriter: {}
|
|
71
|
+
},
|
|
72
|
+
setup(n) {
|
|
73
|
+
return (e, t) => (f(), y("div", de, [
|
|
74
|
+
$("h1", ce, k(typeof n.done.message == "function" ? n.done.message(n.values) : n.done.message), 1)
|
|
75
|
+
]));
|
|
76
|
+
}
|
|
77
|
+
}), fe = /* @__PURE__ */ L(ve, [["__scopeId", "data-v-7802c8f5"]]), me = {
|
|
78
|
+
key: 0,
|
|
79
|
+
class: "ns-error-wrapper"
|
|
80
|
+
}, pe = { class: "ns-error-text" }, ye = /* @__PURE__ */ _({
|
|
81
|
+
__name: "ErrorMessage",
|
|
82
|
+
props: {
|
|
83
|
+
message: {},
|
|
84
|
+
config: {}
|
|
85
|
+
},
|
|
86
|
+
setup(n) {
|
|
87
|
+
return (e, t) => (f(), V(q, { name: "fade-slide" }, {
|
|
88
|
+
default: z(() => [
|
|
89
|
+
n.message ? (f(), y("div", me, [
|
|
90
|
+
$("p", pe, k(n.message), 1)
|
|
91
|
+
])) : F("", !0)
|
|
92
|
+
]),
|
|
93
|
+
_: 1
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
}), ge = /* @__PURE__ */ L(ye, [["__scopeId", "data-v-a8be2d7b"]]), we = { class: "ns-line fade-in" }, be = { class: "ns-prompt-row" }, ke = { class: "ns-prompt" }, Fe = {
|
|
97
|
+
key: 0,
|
|
98
|
+
class: "ns-value"
|
|
99
|
+
}, he = {
|
|
100
|
+
key: 0,
|
|
101
|
+
class: "ns-input-row"
|
|
102
|
+
}, Ce = ["type", "disabled", "value", "onKeydown"], xe = /* @__PURE__ */ _({
|
|
103
|
+
__name: "Line",
|
|
104
|
+
props: {
|
|
105
|
+
field: {},
|
|
106
|
+
status: {},
|
|
107
|
+
value: {},
|
|
108
|
+
allValues: {},
|
|
109
|
+
typewriter: {},
|
|
110
|
+
editable: { type: Boolean },
|
|
111
|
+
locked: { type: Boolean },
|
|
112
|
+
editLabel: {}
|
|
113
|
+
},
|
|
114
|
+
emits: ["typingComplete", "confirm", "edit", "error", "change", "focus", "blur"],
|
|
115
|
+
setup(n, { emit: e }) {
|
|
116
|
+
const t = n, l = e, u = g(null), p = g(""), c = g(t.value ? String(t.value) : ""), i = g(null), h = g(!1);
|
|
117
|
+
let I;
|
|
118
|
+
E(() => t.value, (v) => {
|
|
119
|
+
v !== void 0 && String(v) !== c.value && (c.value = String(v));
|
|
120
|
+
}), E(() => t.status, (v) => {
|
|
121
|
+
if (v === "typing") {
|
|
122
|
+
if (!t.typewriter.enabled) {
|
|
123
|
+
p.value = t.field.prompt, l("typingComplete", t.field.key);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let o = 0;
|
|
127
|
+
I = setInterval(() => {
|
|
128
|
+
p.value = t.field.prompt.slice(0, o + 1), o++, o >= t.field.prompt.length && (clearInterval(I), l("typingComplete", t.field.key));
|
|
129
|
+
}, t.typewriter.speed ?? 30);
|
|
130
|
+
} else (v === "active" || v === "editing") && O(() => {
|
|
131
|
+
var o;
|
|
132
|
+
(o = u.value) == null || o.focus();
|
|
133
|
+
});
|
|
134
|
+
}, { immediate: !0 }), N(() => {
|
|
135
|
+
clearInterval(I);
|
|
136
|
+
});
|
|
137
|
+
const R = (v) => {
|
|
138
|
+
const o = v.target;
|
|
139
|
+
c.value = o.value, l("change", t.field.key, o.value), i.value && (i.value = null);
|
|
140
|
+
}, D = async () => {
|
|
141
|
+
const v = ee(t.field, c.value);
|
|
142
|
+
if (!v.valid) {
|
|
143
|
+
i.value = v.error || "Invalid input", l("error", t.field.key, i.value);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (te(t.field)) {
|
|
147
|
+
h.value = !0;
|
|
148
|
+
const o = await ne(t.field, c.value);
|
|
149
|
+
if (h.value = !1, !o.valid) {
|
|
150
|
+
i.value = o.error || "Invalid input", l("error", t.field.key, i.value);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
i.value = null, l("confirm", t.field.key, c.value);
|
|
155
|
+
};
|
|
156
|
+
return (v, o) => (f(), y("div", we, [
|
|
157
|
+
$("div", be, [
|
|
158
|
+
$("span", ke, k(n.status === "typing" ? p.value : n.field.prompt), 1),
|
|
159
|
+
n.status === "confirmed" ? (f(), y("span", Fe, k(c.value), 1)) : F("", !0),
|
|
160
|
+
n.status === "confirmed" && n.editable && !n.locked ? (f(), y("button", {
|
|
161
|
+
key: 1,
|
|
162
|
+
type: "button",
|
|
163
|
+
class: "ns-edit-btn",
|
|
164
|
+
onClick: o[0] || (o[0] = (B) => l("edit", n.field.key))
|
|
165
|
+
}, k(n.editLabel), 1)) : F("", !0)
|
|
166
|
+
]),
|
|
167
|
+
n.status === "active" || n.status === "editing" ? (f(), y("div", he, [
|
|
168
|
+
$("input", {
|
|
169
|
+
ref_key: "inputRef",
|
|
170
|
+
ref: u,
|
|
171
|
+
type: n.field.type === "email" ? "email" : n.field.type === "password" ? "password" : "text",
|
|
172
|
+
class: "ns-input",
|
|
173
|
+
disabled: h.value,
|
|
174
|
+
value: c.value,
|
|
175
|
+
onInput: R,
|
|
176
|
+
onFocus: o[1] || (o[1] = (B) => l("focus", n.field.key)),
|
|
177
|
+
onBlur: o[2] || (o[2] = (B) => l("blur", n.field.key, c.value)),
|
|
178
|
+
onKeydown: G(J(D, ["prevent"]), ["enter"])
|
|
179
|
+
}, null, 40, Ce)
|
|
180
|
+
])) : F("", !0),
|
|
181
|
+
Q(ge, { message: i.value }, null, 8, ["message"])
|
|
182
|
+
]));
|
|
183
|
+
}
|
|
184
|
+
}), $e = /* @__PURE__ */ L(xe, [["__scopeId", "data-v-d4b482a6"]]), Ie = {
|
|
185
|
+
key: 1,
|
|
186
|
+
class: "ns-form-body"
|
|
187
|
+
}, Se = /* @__PURE__ */ _({
|
|
188
|
+
__name: "NarrativeForm",
|
|
189
|
+
props: {
|
|
190
|
+
fields: {},
|
|
191
|
+
formConfig: {},
|
|
192
|
+
typewriter: {},
|
|
193
|
+
welcome: {},
|
|
194
|
+
done: {},
|
|
195
|
+
editable: { type: Boolean, default: !0 },
|
|
196
|
+
editLabel: {},
|
|
197
|
+
callbacks: {},
|
|
198
|
+
defaultValues: {},
|
|
199
|
+
values: {},
|
|
200
|
+
strings: {},
|
|
201
|
+
reducedMotion: { type: Boolean }
|
|
202
|
+
},
|
|
203
|
+
setup(n) {
|
|
204
|
+
const e = n, t = x(() => e.fields ? e.fields : e.formConfig ? e.formConfig.fields : []), l = x(() => {
|
|
205
|
+
if (e.welcome) return e.welcome;
|
|
206
|
+
if (e.formConfig && "welcome" in e.formConfig) return e.formConfig.welcome;
|
|
207
|
+
}), u = x(() => {
|
|
208
|
+
if (e.done) return e.done;
|
|
209
|
+
if (e.formConfig && "done" in e.formConfig) return e.formConfig.done;
|
|
210
|
+
}), p = x(() => {
|
|
211
|
+
var d;
|
|
212
|
+
return {
|
|
213
|
+
...e.typewriter,
|
|
214
|
+
enabled: e.reducedMotion ? !1 : ((d = e.typewriter) == null ? void 0 : d.enabled) ?? !0
|
|
215
|
+
};
|
|
216
|
+
}), c = x(() => le(e.strings)), {
|
|
217
|
+
snapshot: i,
|
|
218
|
+
startTyping: h,
|
|
219
|
+
activateField: I,
|
|
220
|
+
confirmField: R,
|
|
221
|
+
editField: D,
|
|
222
|
+
reconfirmField: v,
|
|
223
|
+
getMeta: o
|
|
224
|
+
} = se(t.value), B = x(() => {
|
|
225
|
+
var d;
|
|
226
|
+
return ((d = l.value) == null ? void 0 : d.show) !== !1 && l.value !== void 0;
|
|
227
|
+
}), S = g(!B.value), W = g(!1), K = () => {
|
|
228
|
+
if (!S.value || W.value || t.value.length === 0) return;
|
|
229
|
+
W.value = !0;
|
|
230
|
+
const d = t.value.find((a) => i.value.statuses[a.key] !== "confirmed");
|
|
231
|
+
d && h(d.key);
|
|
232
|
+
};
|
|
233
|
+
E(S, K), H(K);
|
|
234
|
+
const P = (d, a) => {
|
|
235
|
+
var m;
|
|
236
|
+
i.value.statuses[d] === "editing" ? v(d, a) : R(d, a), (m = e.callbacks) != null && m.onFieldComplete && e.callbacks.onFieldComplete(d, a, 0);
|
|
237
|
+
const T = t.value.findIndex((r) => r.key === d);
|
|
238
|
+
for (let r = T + 1; r < t.value.length; r++) {
|
|
239
|
+
const s = t.value[r];
|
|
240
|
+
if (!(!s || i.value.statuses[s.key] === "confirmed")) {
|
|
241
|
+
h(s.key);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}, A = x(() => t.value.filter((d) => {
|
|
246
|
+
const a = i.value.statuses[d.key];
|
|
247
|
+
return a === "typing" || a === "active" || a === "confirmed" || a === "editing";
|
|
248
|
+
})), M = g(null);
|
|
249
|
+
return E(() => A.value.length, () => {
|
|
250
|
+
O(() => {
|
|
251
|
+
M.value && (M.value.scrollTop = M.value.scrollHeight);
|
|
252
|
+
});
|
|
253
|
+
}), (d, a) => (f(), y("div", {
|
|
254
|
+
class: "ns-root",
|
|
255
|
+
ref_key: "scrollContainerRef",
|
|
256
|
+
ref: M
|
|
257
|
+
}, [
|
|
258
|
+
!S.value && l.value ? (f(), V(ue, {
|
|
259
|
+
key: 0,
|
|
260
|
+
welcome: l.value,
|
|
261
|
+
typewriter: p.value,
|
|
262
|
+
onStart: a[0] || (a[0] = (C) => S.value = !0)
|
|
263
|
+
}, null, 8, ["welcome", "typewriter"])) : F("", !0),
|
|
264
|
+
S.value ? (f(), y("div", Ie, [
|
|
265
|
+
(f(!0), y(X, null, Y(A.value, (C) => {
|
|
266
|
+
var T;
|
|
267
|
+
return f(), V($e, {
|
|
268
|
+
key: C.key,
|
|
269
|
+
field: C,
|
|
270
|
+
status: b(i).statuses[C.key] ?? "idle",
|
|
271
|
+
value: ((T = e.values) == null ? void 0 : T[C.key]) ?? b(i).values[C.key],
|
|
272
|
+
allValues: b(i).values,
|
|
273
|
+
typewriter: p.value,
|
|
274
|
+
editable: e.editable,
|
|
275
|
+
locked: !1,
|
|
276
|
+
editLabel: e.editLabel ?? c.value.editLabel,
|
|
277
|
+
onTypingComplete: b(I),
|
|
278
|
+
onConfirm: P,
|
|
279
|
+
onEdit: a[1] || (a[1] = (m) => {
|
|
280
|
+
var r, s;
|
|
281
|
+
b(D)(m), (s = (r = e.callbacks) == null ? void 0 : r.onEdit) == null || s.call(r, m);
|
|
282
|
+
}),
|
|
283
|
+
onError: a[2] || (a[2] = (m, r) => {
|
|
284
|
+
var s, w;
|
|
285
|
+
return (w = (s = e.callbacks) == null ? void 0 : s.onError) == null ? void 0 : w.call(s, m, r);
|
|
286
|
+
}),
|
|
287
|
+
onChange: a[3] || (a[3] = (m, r) => {
|
|
288
|
+
var s, w;
|
|
289
|
+
return (w = (s = e.callbacks) == null ? void 0 : s.onChange) == null ? void 0 : w.call(s, m, r);
|
|
290
|
+
}),
|
|
291
|
+
onFocus: a[4] || (a[4] = (m) => {
|
|
292
|
+
var r, s;
|
|
293
|
+
return (s = (r = e.callbacks) == null ? void 0 : r.onFieldFocus) == null ? void 0 : s.call(r, m);
|
|
294
|
+
}),
|
|
295
|
+
onBlur: a[5] || (a[5] = (m, r) => {
|
|
296
|
+
var s, w;
|
|
297
|
+
return (w = (s = e.callbacks) == null ? void 0 : s.onFieldBlur) == null ? void 0 : w.call(s, m, r);
|
|
298
|
+
})
|
|
299
|
+
}, null, 8, ["field", "status", "value", "allValues", "typewriter", "editable", "editLabel", "onTypingComplete"]);
|
|
300
|
+
}), 128))
|
|
301
|
+
])) : F("", !0),
|
|
302
|
+
b(i).isComplete && u.value ? (f(), V(fe, {
|
|
303
|
+
key: 2,
|
|
304
|
+
done: u.value,
|
|
305
|
+
values: b(i).values,
|
|
306
|
+
meta: b(o)(),
|
|
307
|
+
typewriter: p.value
|
|
308
|
+
}, null, 8, ["done", "values", "meta", "typewriter"])) : F("", !0)
|
|
309
|
+
], 512));
|
|
310
|
+
}
|
|
311
|
+
}), Be = /* @__PURE__ */ L(Se, [["__scopeId", "data-v-33050240"]]);
|
|
312
|
+
export {
|
|
313
|
+
fe as DoneScreen,
|
|
314
|
+
ge as ErrorMessage,
|
|
315
|
+
$e as Line,
|
|
316
|
+
Be as NarrativeForm,
|
|
317
|
+
ue as WelcomeScreen,
|
|
318
|
+
se as useFormState
|
|
319
|
+
};
|
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.fade-in[data-v-dbeb46ea]{animation:fadeIn-dbeb46ea .5s ease-out}@keyframes fadeIn-dbeb46ea{0%{opacity:0}to{opacity:1}}.ns-welcome[data-v-dbeb46ea]{padding:2rem}.ns-welcome-title[data-v-dbeb46ea]{font-size:2rem;font-weight:700;margin-bottom:.5rem}.ns-welcome-subtitle[data-v-dbeb46ea]{font-size:1.125rem;opacity:.8;margin-bottom:2rem}.ns-welcome-start[data-v-dbeb46ea]{padding:.75rem 1.5rem;background-color:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:1rem}.fade-in[data-v-7802c8f5]{animation:fadeIn-7802c8f5 .5s ease-out}@keyframes fadeIn-7802c8f5{0%{opacity:0}to{opacity:1}}.ns-done[data-v-7802c8f5]{padding:2rem}.ns-done-title[data-v-7802c8f5]{font-size:1.5rem;font-weight:700;margin-bottom:.5rem}.ns-done-subtitle[data-v-7802c8f5]{font-size:1rem;opacity:.8}.fade-slide-enter-active[data-v-a8be2d7b],.fade-slide-leave-active[data-v-a8be2d7b]{transition:opacity .2s,transform .2s}.fade-slide-enter-from[data-v-a8be2d7b],.fade-slide-leave-to[data-v-a8be2d7b]{opacity:0;transform:translateY(-10px)}.ns-error-text[data-v-a8be2d7b]{color:#d32f2f;font-size:.875rem;margin-top:4px}.fade-in[data-v-d4b482a6]{animation:fadeIn-d4b482a6 .3s ease-out}@keyframes fadeIn-d4b482a6{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:translateY(0)}}.ns-line[data-v-d4b482a6]{margin:1rem 0}.ns-prompt-row[data-v-d4b482a6]{display:flex;align-items:center;flex-wrap:wrap}.ns-prompt[data-v-d4b482a6]{font-size:1.125rem;margin-right:.5rem}.ns-value[data-v-d4b482a6]{font-size:1.125rem;font-weight:600;color:#007bff;margin-right:.5rem}.ns-edit-btn[data-v-d4b482a6]{margin-left:.5rem;padding:.25rem .5rem;border-radius:4px;background-color:#0000000d;border:none;cursor:pointer;font-size:.75rem;font-weight:700}.ns-input-row[data-v-d4b482a6]{margin-top:.5rem}.ns-input[data-v-d4b482a6]{font-size:1.125rem;padding:.5rem 0;border:none;border-bottom:2px solid #007bff;outline:none;width:100%;background:transparent}.ns-input[data-v-d4b482a6]:disabled{border-bottom-color:#ccc}.ns-root[data-v-33050240]{display:flex;flex-direction:column;height:100%;overflow-y:auto;padding:1.25rem}.ns-form-body[data-v-33050240]{flex-grow:1}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@viveksinghind/narrative-form-vue",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Vue 3 components for narrative-form — typewriter-style sign-up flow",
|
|
5
|
+
"author": "Vivek Singh <vivekSinghInd>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.cjs",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"sideEffects": false,
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.cjs",
|
|
18
|
+
"types": "./dist/index.d.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@viveksinghind/narrative-form-core": "1.0.2"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"vue": ">=3.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
29
|
+
"rimraf": "^5.0.5",
|
|
30
|
+
"typescript": "^5.4.5",
|
|
31
|
+
"vite": "^5.2.11",
|
|
32
|
+
"vite-plugin-dts": "^5.0.3",
|
|
33
|
+
"vue": "^3.4.27",
|
|
34
|
+
"vue-tsc": "^2.0.16"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"vue",
|
|
38
|
+
"vue3",
|
|
39
|
+
"narrative-form",
|
|
40
|
+
"typewriter",
|
|
41
|
+
"form",
|
|
42
|
+
"onboarding",
|
|
43
|
+
"signup",
|
|
44
|
+
"typescript"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/vivekSinghInd/narrative-form.git",
|
|
49
|
+
"directory": "packages/vue"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "vite build",
|
|
53
|
+
"dev": "vite build --watch",
|
|
54
|
+
"typecheck": "vue-tsc --noEmit",
|
|
55
|
+
"clean": "rimraf dist"
|
|
56
|
+
}
|
|
57
|
+
}
|