@queuezero/vue 0.1.8 → 0.1.10

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
@@ -77,9 +77,32 @@ Ready-to-use signup form.
77
77
  <input :value="email" @input="setEmail($event.target.value)" />
78
78
  <button @click="submit">{{ loading ? '...' : 'Join' }}</button>
79
79
  </WaitlistForm>
80
+
81
+ <!-- Modal mode -->
82
+ <WaitlistForm
83
+ display-mode="modal"
84
+ trigger-text="Join Our Waitlist"
85
+ modal-size="md"
86
+ <!-- Headless modal mode -->
87
+ <WaitlistForm
88
+ display-mode="modal"
89
+ attach-to-text="Join Waitlist"
90
+ modal-size="md"
91
+ />
80
92
  </template>
81
93
  ```
82
94
 
95
+ #### Modal Mode Props
96
+
97
+ | Prop | Type | Default | Description |
98
+ |------|------|---------|-------------|
99
+ | `display-mode` | `'inline' \| 'modal'` | `'inline'` | Display as inline form or modal |
100
+ | `trigger-text` | `string` | `'Join Waitlist'` | Button text (modal mode only) |
101
+ | `attach-to-text` | `string` | - | Text to match for auto-attaching modal (headless mode) |
102
+ | `modal-size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Modal width (modal mode only) |
103
+
104
+ The modal automatically uses your campaign's branding (logo, cover image, theme color) from the API.
105
+
83
106
  ### WaitlistStatus
84
107
 
85
108
  Displays position, score, and referral count.
package/dist/index.d.mts CHANGED
@@ -62,6 +62,10 @@ interface WaitlistFormProps {
62
62
  triggerText?: string;
63
63
  /** Modal size (only used when displayMode is 'modal') */
64
64
  modalSize?: 'sm' | 'md' | 'lg';
65
+ /** CSS selector to attach modal trigger to existing elements */
66
+ triggerSelector?: string;
67
+ /** Text content to match for attaching modal trigger to <a> and <button> elements */
68
+ attachToText?: string;
65
69
  }
66
70
  /**
67
71
  * Props for WaitlistStatus component
@@ -218,11 +222,19 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
218
222
  type: PropType<"sm" | "md" | "lg">;
219
223
  default: string;
220
224
  };
225
+ triggerSelector: {
226
+ type: StringConstructor;
227
+ default: undefined;
228
+ };
229
+ attachToText: {
230
+ type: StringConstructor;
231
+ default: undefined;
232
+ };
221
233
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
222
234
  [key: string]: any;
223
235
  }> | VNode<vue.RendererNode, vue.RendererElement, {
224
236
  [key: string]: any;
225
- }>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
237
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
226
238
  success: (response: SubmitResponse) => true;
227
239
  error: (error: Error) => true;
228
240
  }, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -266,6 +278,14 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
266
278
  type: PropType<"sm" | "md" | "lg">;
267
279
  default: string;
268
280
  };
281
+ triggerSelector: {
282
+ type: StringConstructor;
283
+ default: undefined;
284
+ };
285
+ attachToText: {
286
+ type: StringConstructor;
287
+ default: undefined;
288
+ };
269
289
  }>> & Readonly<{
270
290
  onSuccess?: ((response: SubmitResponse) => any) | undefined;
271
291
  onError?: ((error: Error) => any) | undefined;
@@ -280,6 +300,8 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
280
300
  displayMode: "inline" | "modal";
281
301
  triggerText: string;
282
302
  modalSize: "sm" | "md" | "lg";
303
+ triggerSelector: string;
304
+ attachToText: string;
283
305
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
284
306
 
285
307
  /**
package/dist/index.d.ts CHANGED
@@ -62,6 +62,10 @@ interface WaitlistFormProps {
62
62
  triggerText?: string;
63
63
  /** Modal size (only used when displayMode is 'modal') */
64
64
  modalSize?: 'sm' | 'md' | 'lg';
65
+ /** CSS selector to attach modal trigger to existing elements */
66
+ triggerSelector?: string;
67
+ /** Text content to match for attaching modal trigger to <a> and <button> elements */
68
+ attachToText?: string;
65
69
  }
66
70
  /**
67
71
  * Props for WaitlistStatus component
@@ -218,11 +222,19 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
218
222
  type: PropType<"sm" | "md" | "lg">;
219
223
  default: string;
220
224
  };
225
+ triggerSelector: {
226
+ type: StringConstructor;
227
+ default: undefined;
228
+ };
229
+ attachToText: {
230
+ type: StringConstructor;
231
+ default: undefined;
232
+ };
221
233
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
222
234
  [key: string]: any;
223
235
  }> | VNode<vue.RendererNode, vue.RendererElement, {
224
236
  [key: string]: any;
225
- }>[] | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
237
+ }>[] | null | undefined, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {
226
238
  success: (response: SubmitResponse) => true;
227
239
  error: (error: Error) => true;
228
240
  }, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -266,6 +278,14 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
266
278
  type: PropType<"sm" | "md" | "lg">;
267
279
  default: string;
268
280
  };
281
+ triggerSelector: {
282
+ type: StringConstructor;
283
+ default: undefined;
284
+ };
285
+ attachToText: {
286
+ type: StringConstructor;
287
+ default: undefined;
288
+ };
269
289
  }>> & Readonly<{
270
290
  onSuccess?: ((response: SubmitResponse) => any) | undefined;
271
291
  onError?: ((error: Error) => any) | undefined;
@@ -280,6 +300,8 @@ declare const WaitlistForm: vue.DefineComponent<vue.ExtractPropTypes<{
280
300
  displayMode: "inline" | "modal";
281
301
  triggerText: string;
282
302
  modalSize: "sm" | "md" | "lg";
303
+ triggerSelector: string;
304
+ attachToText: string;
283
305
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
284
306
 
285
307
  /**
package/dist/index.js CHANGED
@@ -76,7 +76,13 @@ function useWaitlist(campaign, config) {
76
76
  join: stateManager.join.bind(stateManager),
77
77
  refresh: stateManager.refresh.bind(stateManager),
78
78
  fetchPublicConfig: stateManager.fetchPublicConfig.bind(stateManager),
79
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
79
+ getReferralLink: () => {
80
+ const code = stateManager.getReferralCode();
81
+ if (code && typeof window !== "undefined") {
82
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
83
+ }
84
+ return stateManager.getReferralLink();
85
+ },
80
86
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
81
87
  reset: stateManager.reset.bind(stateManager)
82
88
  };
@@ -147,6 +153,14 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
147
153
  modalSize: {
148
154
  type: String,
149
155
  default: "md"
156
+ },
157
+ triggerSelector: {
158
+ type: String,
159
+ default: void 0
160
+ },
161
+ attachToText: {
162
+ type: String,
163
+ default: void 0
150
164
  }
151
165
  },
152
166
  emits: {
@@ -158,6 +172,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
158
172
  const email = (0, import_vue2.ref)("");
159
173
  const localError = (0, import_vue2.ref)(null);
160
174
  const isModalOpen = (0, import_vue2.ref)(false);
175
+ const successData = (0, import_vue2.ref)(null);
161
176
  const formData = (0, import_vue2.reactive)({});
162
177
  const displayError = (0, import_vue2.computed)(() => localError.value || contextError.value);
163
178
  const branding = (0, import_vue2.computed)(() => publicConfig.value?.branding || {});
@@ -169,6 +184,32 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
169
184
  const controller = (0, import_vue2.computed)(
170
185
  () => new import_queuezero2.FormController(join, formFields.value, props.referrerCode)
171
186
  );
187
+ const cleanupFns = [];
188
+ (0, import_vue2.onMounted)(() => {
189
+ if (props.displayMode !== "modal") return;
190
+ const attach = (element) => {
191
+ const handler = (e) => {
192
+ e.preventDefault();
193
+ isModalOpen.value = true;
194
+ };
195
+ element.addEventListener("click", handler);
196
+ cleanupFns.push(() => element.removeEventListener("click", handler));
197
+ };
198
+ if (props.triggerSelector) {
199
+ document.querySelectorAll(props.triggerSelector).forEach(attach);
200
+ }
201
+ if (props.attachToText) {
202
+ const lowerText = props.attachToText.toLowerCase();
203
+ document.querySelectorAll("a, button").forEach((el) => {
204
+ if (el.textContent?.toLowerCase().includes(lowerText)) {
205
+ attach(el);
206
+ }
207
+ });
208
+ }
209
+ });
210
+ (0, import_vue2.onUnmounted)(() => {
211
+ cleanupFns.forEach((fn) => fn());
212
+ });
172
213
  async function handleSubmit(e) {
173
214
  e?.preventDefault();
174
215
  localError.value = null;
@@ -177,6 +218,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
177
218
  if (result) {
178
219
  email.value = "";
179
220
  Object.keys(formData).forEach((key) => delete formData[key]);
221
+ successData.value = result;
180
222
  emit("success", result);
181
223
  } else if (contextError.value) {
182
224
  emit("error", contextError.value);
@@ -210,7 +252,7 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
210
252
  formData[field.key] = e.target.value;
211
253
  }
212
254
  }, [
213
- (0, import_vue2.h)("option", { value: "" }, field.placeholder || `Select ${field.label}`),
255
+ (0, import_vue2.h)("option", { value: "" }, "Select an option"),
214
256
  ...(field.options || []).map(
215
257
  (opt) => (0, import_vue2.h)("option", { value: opt }, opt)
216
258
  )
@@ -343,9 +385,10 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
343
385
  if (props.displayMode === "inline") {
344
386
  return formContent;
345
387
  }
346
- return (0, import_vue2.h)("div", { class: "qz-waitlist-modal-wrapper" }, [
347
- // Trigger button
348
- (0, import_vue2.h)(
388
+ const isHeadless = !!(props.triggerSelector || props.attachToText);
389
+ const children = [];
390
+ if (!isHeadless) {
391
+ children.push((0, import_vue2.h)(
349
392
  "button",
350
393
  {
351
394
  type: "button",
@@ -356,9 +399,10 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
356
399
  }
357
400
  },
358
401
  props.triggerText
359
- ),
360
- // Modal overlay (only when open)
361
- isModalOpen.value && (0, import_vue2.h)(
402
+ ));
403
+ }
404
+ if (isModalOpen.value) {
405
+ children.push((0, import_vue2.h)(
362
406
  "div",
363
407
  {
364
408
  class: "qz-modal-overlay",
@@ -389,12 +433,25 @@ var WaitlistForm = (0, import_vue2.defineComponent)({
389
433
  // Body
390
434
  (0, import_vue2.h)("div", { class: "qz-modal-body" }, [
391
435
  (0, import_vue2.h)("h2", { class: "qz-modal-title" }, publicConfig.value?.name || "Join Waitlist"),
392
- formContent
436
+ successData.value ? (0, import_vue2.h)("div", { class: "qz-success" }, [
437
+ (0, import_vue2.h)("h3", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "0.5rem", textAlign: "center" } }, "You're on the list!"),
438
+ (0, import_vue2.h)("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: "0.9" } }, `You're #${successData.value.position || "?"} in line.`),
439
+ successData.value.referralLink && (0, import_vue2.h)("div", { style: { marginTop: "1.5rem" } }, [
440
+ (0, import_vue2.h)("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: "0.9", textAlign: "center" } }, "Refer friends to move up:"),
441
+ (0, import_vue2.h)("input", {
442
+ class: "qz-form-input",
443
+ style: { textAlign: "center", cursor: "pointer" },
444
+ readonly: true,
445
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.value.referralCode,
446
+ onClick: (e) => e.target.select()
447
+ })
448
+ ])
449
+ ]) : formContent
393
450
  ])
394
451
  ])
395
452
  ]
396
- )
397
- ]);
453
+ ));
454
+ }
398
455
  };
399
456
  }
400
457
  });
package/dist/index.mjs CHANGED
@@ -40,7 +40,13 @@ function useWaitlist(campaign, config) {
40
40
  join: stateManager.join.bind(stateManager),
41
41
  refresh: stateManager.refresh.bind(stateManager),
42
42
  fetchPublicConfig: stateManager.fetchPublicConfig.bind(stateManager),
43
- getReferralLink: stateManager.getReferralLink.bind(stateManager),
43
+ getReferralLink: () => {
44
+ const code = stateManager.getReferralCode();
45
+ if (code && typeof window !== "undefined") {
46
+ return `${window.location.origin}${window.location.pathname}?ref=${code}`;
47
+ }
48
+ return stateManager.getReferralLink();
49
+ },
44
50
  getReferralCode: stateManager.getReferralCode.bind(stateManager),
45
51
  reset: stateManager.reset.bind(stateManager)
46
52
  };
@@ -67,7 +73,7 @@ function createWaitlistPlugin(options) {
67
73
  }
68
74
 
69
75
  // src/WaitlistForm.ts
70
- import { defineComponent, ref as ref2, computed as computed2, h, reactive } from "vue";
76
+ import { defineComponent, ref as ref2, computed as computed2, h, reactive, onMounted as onMounted2, onUnmounted } from "vue";
71
77
  import { FormController } from "queuezero";
72
78
  var WaitlistForm = defineComponent({
73
79
  name: "WaitlistForm",
@@ -111,6 +117,14 @@ var WaitlistForm = defineComponent({
111
117
  modalSize: {
112
118
  type: String,
113
119
  default: "md"
120
+ },
121
+ triggerSelector: {
122
+ type: String,
123
+ default: void 0
124
+ },
125
+ attachToText: {
126
+ type: String,
127
+ default: void 0
114
128
  }
115
129
  },
116
130
  emits: {
@@ -122,6 +136,7 @@ var WaitlistForm = defineComponent({
122
136
  const email = ref2("");
123
137
  const localError = ref2(null);
124
138
  const isModalOpen = ref2(false);
139
+ const successData = ref2(null);
125
140
  const formData = reactive({});
126
141
  const displayError = computed2(() => localError.value || contextError.value);
127
142
  const branding = computed2(() => publicConfig.value?.branding || {});
@@ -133,6 +148,32 @@ var WaitlistForm = defineComponent({
133
148
  const controller = computed2(
134
149
  () => new FormController(join, formFields.value, props.referrerCode)
135
150
  );
151
+ const cleanupFns = [];
152
+ onMounted2(() => {
153
+ if (props.displayMode !== "modal") return;
154
+ const attach = (element) => {
155
+ const handler = (e) => {
156
+ e.preventDefault();
157
+ isModalOpen.value = true;
158
+ };
159
+ element.addEventListener("click", handler);
160
+ cleanupFns.push(() => element.removeEventListener("click", handler));
161
+ };
162
+ if (props.triggerSelector) {
163
+ document.querySelectorAll(props.triggerSelector).forEach(attach);
164
+ }
165
+ if (props.attachToText) {
166
+ const lowerText = props.attachToText.toLowerCase();
167
+ document.querySelectorAll("a, button").forEach((el) => {
168
+ if (el.textContent?.toLowerCase().includes(lowerText)) {
169
+ attach(el);
170
+ }
171
+ });
172
+ }
173
+ });
174
+ onUnmounted(() => {
175
+ cleanupFns.forEach((fn) => fn());
176
+ });
136
177
  async function handleSubmit(e) {
137
178
  e?.preventDefault();
138
179
  localError.value = null;
@@ -141,6 +182,7 @@ var WaitlistForm = defineComponent({
141
182
  if (result) {
142
183
  email.value = "";
143
184
  Object.keys(formData).forEach((key) => delete formData[key]);
185
+ successData.value = result;
144
186
  emit("success", result);
145
187
  } else if (contextError.value) {
146
188
  emit("error", contextError.value);
@@ -174,7 +216,7 @@ var WaitlistForm = defineComponent({
174
216
  formData[field.key] = e.target.value;
175
217
  }
176
218
  }, [
177
- h("option", { value: "" }, field.placeholder || `Select ${field.label}`),
219
+ h("option", { value: "" }, "Select an option"),
178
220
  ...(field.options || []).map(
179
221
  (opt) => h("option", { value: opt }, opt)
180
222
  )
@@ -307,9 +349,10 @@ var WaitlistForm = defineComponent({
307
349
  if (props.displayMode === "inline") {
308
350
  return formContent;
309
351
  }
310
- return h("div", { class: "qz-waitlist-modal-wrapper" }, [
311
- // Trigger button
312
- h(
352
+ const isHeadless = !!(props.triggerSelector || props.attachToText);
353
+ const children = [];
354
+ if (!isHeadless) {
355
+ children.push(h(
313
356
  "button",
314
357
  {
315
358
  type: "button",
@@ -320,9 +363,10 @@ var WaitlistForm = defineComponent({
320
363
  }
321
364
  },
322
365
  props.triggerText
323
- ),
324
- // Modal overlay (only when open)
325
- isModalOpen.value && h(
366
+ ));
367
+ }
368
+ if (isModalOpen.value) {
369
+ children.push(h(
326
370
  "div",
327
371
  {
328
372
  class: "qz-modal-overlay",
@@ -353,12 +397,25 @@ var WaitlistForm = defineComponent({
353
397
  // Body
354
398
  h("div", { class: "qz-modal-body" }, [
355
399
  h("h2", { class: "qz-modal-title" }, publicConfig.value?.name || "Join Waitlist"),
356
- formContent
400
+ successData.value ? h("div", { class: "qz-success" }, [
401
+ h("h3", { style: { fontSize: "1.25rem", fontWeight: "600", marginBottom: "0.5rem", textAlign: "center" } }, "You're on the list!"),
402
+ h("p", { style: { textAlign: "center", marginBottom: "1.5rem", opacity: "0.9" } }, `You're #${successData.value.position || "?"} in line.`),
403
+ successData.value.referralLink && h("div", { style: { marginTop: "1.5rem" } }, [
404
+ h("p", { style: { marginBottom: "0.5rem", fontSize: "0.875rem", opacity: "0.9", textAlign: "center" } }, "Refer friends to move up:"),
405
+ h("input", {
406
+ class: "qz-form-input",
407
+ style: { textAlign: "center", cursor: "pointer" },
408
+ readonly: true,
409
+ value: window.location.origin + window.location.pathname + "?ref=" + successData.value.referralCode,
410
+ onClick: (e) => e.target.select()
411
+ })
412
+ ])
413
+ ]) : formContent
357
414
  ])
358
415
  ])
359
416
  ]
360
- )
361
- ]);
417
+ ));
418
+ }
362
419
  };
363
420
  }
364
421
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@queuezero/vue",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Vue components and composables for QueueZero viral waitlists",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",