@codebards/ik-embeddable-form 0.0.2619768040-qa

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +494 -0
  2. package/dist/embed.js +4 -0
  3. package/package.json +47 -0
package/README.md ADDED
@@ -0,0 +1,494 @@
1
+ # IK Embeddable Form
2
+
3
+ A production-ready, config-driven embeddable form platform built with **Preact**, **TypeScript**, **Vite**, **Tailwind CSS**, and **Shadow DOM** isolation.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Architecture Overview](#architecture-overview)
10
+ 2. [Installation](#installation)
11
+ 3. [Script Usage](#script-usage)
12
+ 4. [Config Schema](#config-schema)
13
+ 5. [Environment Setup](#environment-setup)
14
+ 6. [Build & Deployment](#build--deployment)
15
+ 7. [Versioning Strategy](#versioning-strategy)
16
+ 8. [Adding New Form Variants](#adding-new-form-variants)
17
+ 9. [Analytics Integration](#analytics-integration)
18
+ 10. [API Integration](#api-integration)
19
+
20
+ ---
21
+
22
+ ## Architecture Overview
23
+
24
+ ```
25
+ src/
26
+ ├── core/
27
+ │ ├── FormEngine/ # Orchestrates step transitions, state, API calls
28
+ │ ├── StepResolver/ # Evaluates conditions, resolves next steps
29
+ │ ├── ConfigResolver/ # Resolves FormConfig from OpenConfig + variants
30
+ │ ├── ApiClient/ # HTTP client with retries, body/response mapping
31
+ │ └── Analytics/ # GTM · Sentry · Clarity · Cookie facades
32
+
33
+ ├── components/
34
+ │ ├── Modal/ # Accessible modal with backdrop + progress bar
35
+ │ ├── FormSteps/ # StepRenderer · AutoStep · SuccessStep · FieldRenderer
36
+ │ └── Shared/ # Button · Input · Select · Textarea · Radio · Checkbox
37
+
38
+ ├── configs/
39
+ │ ├── sample-webinar.config.ts # Multi-step webinar registration
40
+ │ └── sample-contact.config.ts # Conditional contact form
41
+
42
+ ├── hooks/
43
+ │ ├── useFormEngine.ts # Subscribes to FormEngine state
44
+ │ └── useAnalytics.ts # Analytics action hooks
45
+
46
+ ├── styles/
47
+ │ └── base.css # Tailwind directives + Shadow DOM resets
48
+
49
+ ├── types/
50
+ │ └── index.ts # FormConfig · FormStep · ValidationRule · ApiContract · AnalyticsEvent
51
+
52
+ ├── App.tsx # Root Preact component
53
+ └── sdk/
54
+ ├── index.ts # window.IKForm global API
55
+ └── mount.ts # Shadow DOM mount/unmount/destroy
56
+ ```
57
+
58
+ ### Shadow DOM Isolation
59
+
60
+ The form is mounted inside a **Shadow DOM** attached to a `<div id="ik-form-host">` injected into `document.body`. This means:
61
+
62
+ - Global CSS on the host page **cannot** bleed into the form
63
+ - Form styles **cannot** leak out to the host page
64
+ - No class name or selector conflicts
65
+
66
+ ---
67
+
68
+ ## Installation
69
+
70
+ ### Via CDN (Recommended)
71
+
72
+ ```html
73
+ <script src="https://cdn.example.com/forms/v1/embed.js"></script>
74
+ ```
75
+
76
+ ### Self-hosted
77
+
78
+ ```bash
79
+ # Build for production
80
+ npm run build:production
81
+
82
+ # Upload dist/embed.js to your CDN / server
83
+ ```
84
+
85
+ ### npm (for framework integration)
86
+
87
+ ```bash
88
+ npm install ik-embeddable-form
89
+ ```
90
+
91
+ ```ts
92
+ import { IKForm } from 'ik-embeddable-form';
93
+
94
+ IKForm.open({ eventName: 'my-webinar', webinarType: 'live', ... });
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Script Usage
100
+
101
+ ### Basic
102
+
103
+ ```html
104
+ <script src="https://cdn.example.com/forms/embed.js"></script>
105
+ <script>
106
+ window.IKForm.open({
107
+ eventName: 'ai-summit-2026',
108
+ webinarType: 'live',
109
+ site: 'main',
110
+ variant: 'default',
111
+ });
112
+ </script>
113
+ ```
114
+
115
+ ### With All Options
116
+
117
+ ```ts
118
+ window.IKForm.open({
119
+ // Required
120
+ eventName: 'ai-summit-2026', // Matches FormConfig.eventName
121
+ webinarType: 'live', // Matches FormConfig.webinarType
122
+ site: 'main', // Multi-site identifier (forwarded to API)
123
+ variant: 'compact', // Must match a FormVariant.id (or 'default')
124
+
125
+ // Optional
126
+ preferredSlot: 'slot-morning', // Pre-selects a slot field
127
+
128
+ prefilledValues: { // Pre-fills form fields by name
129
+ email: 'user@example.com',
130
+ firstName: 'Jane',
131
+ },
132
+
133
+ configOverrides: { // Runtime overrides merged on top of config
134
+ theme: { modalMaxWidth: '600px' },
135
+ },
136
+
137
+ // Callbacks
138
+ onSuccess: (result) => {
139
+ console.log('Form submitted:', result.data);
140
+ console.log('Ticket/Reg ID:', result.data.registrationId);
141
+ },
142
+ onClose: () => {
143
+ console.log('Modal closed');
144
+ },
145
+ onError: (err) => {
146
+ console.error('Form error:', err.code, err.message);
147
+ },
148
+ });
149
+ ```
150
+
151
+ ### Programmatic Control
152
+
153
+ ```ts
154
+ // Close the modal
155
+ window.IKForm.close();
156
+
157
+ // Full cleanup (SPA route change)
158
+ window.IKForm.destroy();
159
+
160
+ // Get loaded version
161
+ console.log(window.IKForm.getVersion()); // "1.0.0"
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Config Schema
167
+
168
+ ### `FormConfig` (root)
169
+
170
+ | Property | Type | Required | Description |
171
+ |------------------|-------------------|----------|--------------------------------------------------|
172
+ | `id` | `string` | ✅ | Unique config identifier |
173
+ | `name` | `string` | ✅ | Human-readable form name |
174
+ | `version` | `string` | ✅ | Semver (e.g. `"1.2.0"`) |
175
+ | `eventName` | `string` | ✅ | Matches `OpenConfig.eventName` |
176
+ | `webinarType` | `string` | – | Matches `OpenConfig.webinarType` |
177
+ | `defaultVariant` | `string` | ✅ | Fallback variant ID |
178
+ | `variants` | `FormVariant[]` | – | Named overrides applied on top of base config |
179
+ | `steps` | `FormStep[]` | ✅ | Ordered step definitions |
180
+ | `api` | `ApiContract` | ✅ | Default submission endpoint |
181
+ | `analytics` | `AnalyticsConfig` | – | Open/close/submit event config |
182
+ | `theme` | `ThemeConfig` | – | Visual customisation |
183
+ | `i18n` | `Record<string, string>` | – | Localisation strings |
184
+ | `metadata` | `Record<string, string \| number \| boolean>` | – | Forwarded to analytics |
185
+
186
+ ### `FormStep`
187
+
188
+ | Property | Type | Description |
189
+ |------------------|----------------------------------|--------------------------------------------------|
190
+ | `id` | `string` | Unique step identifier |
191
+ | `type` | `"form" \| "auto" \| "success" \| "error" \| "info"` | Step type |
192
+ | `title` | `string?` | Step heading |
193
+ | `subtitle` | `string?` | Step sub-heading |
194
+ | `hidden` | `boolean?` | Hide from progress indicator |
195
+ | `condition` | `ConditionalRule?` | Step only reachable when condition passes |
196
+ | `autoExecute` | `AutoExecuteConfig?` | API call fired automatically on step entry |
197
+ | `fields` | `FormField[]?` | Input fields rendered in this step |
198
+ | `api` | `ApiContract?` | Per-step API call (fires on Next) |
199
+ | `nextStep` | `string \| ConditionalNextStep[]?` | Fixed or conditional next step routing |
200
+ | `allowBack` | `boolean?` | Show Back button (default: `true`) |
201
+ | `submitLabel` | `string?` | Override CTA label |
202
+ | `analyticsEvents`| `AnalyticsEvent[]?` | Events fired when step becomes active |
203
+
204
+ ### `FormField`
205
+
206
+ | Property | Type | Description |
207
+ |----------------|-------------------|--------------------------------------------------|
208
+ | `name` | `string` | Field name (key in form data) |
209
+ | `type` | `FieldType` | `text \| email \| tel \| number \| textarea \| select \| radio \| checkbox \| date \| hidden \| slot-picker` |
210
+ | `label` | `string?` | Label text |
211
+ | `placeholder` | `string?` | Placeholder text |
212
+ | `defaultValue` | `string \| number \| boolean?` | Initial value |
213
+ | `options` | `FieldOption[]?` | Options for select/radio |
214
+ | `validation` | `ValidationRule[]?` | Array of validation rules |
215
+ | `condition` | `ConditionalRule?` | Show/hide field based on other field values |
216
+ | `colSpan` | `number?` | Grid column span (1 = half, 2 = full) |
217
+
218
+ ### `ValidationRule`
219
+
220
+ | Property | Type | Description |
221
+ |--------------|------------------|---------------------------------------------------|
222
+ | `type` | `ValidationType` | `required \| email \| phone \| min \| max \| minLength \| maxLength \| pattern \| custom \| async` |
223
+ | `value` | `number \| string?` | Threshold or pattern string |
224
+ | `message` | `string` | Error message shown to user |
225
+ | `condition` | `ConditionalRule?` | Only apply this rule when condition is true |
226
+
227
+ ### `ApiContract`
228
+
229
+ | Property | Type | Description |
230
+ |-------------------|-----------------------|--------------------------------------------------|
231
+ | `endpoint` | `string` | Relative path or full URL. Supports `{fieldName}` interpolation |
232
+ | `method` | `HttpMethod` | `GET \| POST \| PUT \| PATCH \| DELETE` |
233
+ | `bodyMapping` | `Record<string,string> \| "$all"` | Map form fields → request body keys |
234
+ | `responseMapping` | `Record<string,string>?` | Map response JSON paths → form field names |
235
+ | `queryParams` | `Record<string,string>?` | Query params with `{fieldName}` interpolation|
236
+ | `authenticated` | `boolean?` | Include `Authorization: Bearer` header |
237
+ | `retries` | `number?` | Retry count (default: 1) |
238
+ | `timeoutMs` | `number?` | Request timeout (default: 10,000ms) |
239
+ | `mockResponse` | `unknown?` | Used in `local` env instead of real network call |
240
+
241
+ ### `ConditionalRule`
242
+
243
+ ```ts
244
+ {
245
+ logical?: "AND" | "OR", // default: "AND"
246
+ clauses: [
247
+ {
248
+ field: "fieldName", // dot notation supported: "address.city"
249
+ operator: "equals" | "not_equals" | "contains" | "not_contains" |
250
+ "starts_with" | "ends_with" | "greater_than" | "less_than" |
251
+ "is_empty" | "is_not_empty" | "matches_regex",
252
+ value?: "some value" // not needed for is_empty / is_not_empty
253
+ }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ ---
259
+
260
+ ## Environment Setup
261
+
262
+ | File | Used For |
263
+ |-------------------|------------------------|
264
+ | `.env` | Local development |
265
+ | `.env.staging` | Staging builds |
266
+ | `.env.production` | Production builds |
267
+
268
+ ### Required Variables
269
+
270
+ ```ini
271
+ VITE_API_BASE_URL=https://api.example.com/api
272
+ VITE_GTM_ID=GTM-XXXXXXX
273
+ VITE_SENTRY_DSN=https://xxx@sentry.io/xxx
274
+ VITE_CLARITY_ID=xxxxxxxxxx
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Build & Deployment
280
+
281
+ ### Local Dev Server
282
+
283
+ ```bash
284
+ npm install
285
+ npm run dev
286
+ # Opens http://localhost:3000 with a test page
287
+ ```
288
+
289
+ ### Build Commands
290
+
291
+ ```bash
292
+ npm run build # Builds with .env (local defaults)
293
+ npm run build:staging # Builds with .env.staging
294
+ npm run build:production # Builds with .env.production (minified)
295
+ ```
296
+
297
+ ### Build Output
298
+
299
+ ```
300
+ dist/
301
+ ├── embed.js # Self-contained IIFE bundle (reference in <script>)
302
+ └── embed.js.map # Source map (only in staging/local builds)
303
+ ```
304
+
305
+ ### Deployment Checklist
306
+
307
+ 1. Run `npm run build:production`
308
+ 2. Upload `dist/embed.js` to your CDN with a versioned path:
309
+ ```
310
+ https://cdn.example.com/forms/v1.0.0/embed.js
311
+ ```
312
+ 3. Set cache headers:
313
+ - `embed.js` → `Cache-Control: public, max-age=31536000, immutable` (versioned path)
314
+ - Use a `latest` alias for convenience: `cdn.example.com/forms/latest/embed.js` with shorter TTL
315
+
316
+ 4. Reference on the host page:
317
+ ```html
318
+ <script src="https://cdn.example.com/forms/v1.0.0/embed.js" async></script>
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Versioning Strategy
324
+
325
+ This project follows **Semantic Versioning** (`MAJOR.MINOR.PATCH`):
326
+
327
+ | Change Type | Version Bump | Example |
328
+ |----------------------|-------------|---------|
329
+ | Breaking API change | MAJOR | `2.0.0` |
330
+ | New form variant | MINOR | `1.1.0` |
331
+ | Bug fix / patch | PATCH | `1.0.1` |
332
+
333
+ ### Release Process
334
+
335
+ ```bash
336
+ # 1. Bump version in package.json
337
+ npm version patch # or minor / major
338
+
339
+ # 2. Build production artefact
340
+ npm run build:production
341
+
342
+ # 3. Tag the release
343
+ git tag v$(node -p "require('./package.json').version")
344
+ git push --tags
345
+
346
+ # 4. Upload dist/embed.js to CDN under versioned path
347
+ ```
348
+
349
+ The `__VERSION__` constant is automatically injected from `package.json` at build time.
350
+
351
+ ---
352
+
353
+ ## Adding New Form Variants
354
+
355
+ ### Option A — New Variant on Existing Config
356
+
357
+ Add a `FormVariant` to the `variants` array of an existing `FormConfig`:
358
+
359
+ ```ts
360
+ // src/configs/sample-webinar.config.ts
361
+ variants: [
362
+ {
363
+ id: 'enterprise',
364
+ overrides: {
365
+ steps: [ /* override any step */ ],
366
+ theme: { modalMaxWidth: '680px' },
367
+ },
368
+ },
369
+ ],
370
+ ```
371
+
372
+ Then call:
373
+ ```ts
374
+ IKForm.open({ eventName: 'ai-summit-2026', webinarType: 'live', variant: 'enterprise' });
375
+ ```
376
+
377
+ ### Option B — New FormConfig File
378
+
379
+ 1. Create `src/configs/my-new-form.config.ts`
380
+ 2. Export a `FormConfig` object with a unique `eventName` + `webinarType`
381
+ 3. Register it in `src/configs/index.ts`:
382
+
383
+ ```ts
384
+ import { myNewFormConfig } from './my-new-form.config';
385
+
386
+ export const ALL_CONFIGS: FormConfig[] = [
387
+ webinarConfig,
388
+ contactConfig,
389
+ myNewFormConfig, // ← add here
390
+ ];
391
+ ```
392
+
393
+ No other files need to change. The platform supports **25+ variants** this way.
394
+
395
+ ---
396
+
397
+ ## Analytics Integration
398
+
399
+ ### GTM
400
+
401
+ Events are pushed to `window.dataLayer` automatically. Configure your GTM triggers on:
402
+
403
+ - `form_open` — fired when modal opens
404
+ - `form_close` — fired when modal closes
405
+ - `form_step_view` — fired on each step
406
+ - `form_step_complete` — fired after Next/Submit per step
407
+ - `form_submit_success` — fired on final submission
408
+ - `form_submit_error` — fired on submission failure
409
+
410
+ ### Sentry
411
+
412
+ Install `@sentry/browser` and uncomment the placeholder code in `src/core/Analytics/sentry.ts`. The DSN is injected from `VITE_SENTRY_DSN`.
413
+
414
+ ### Clarity
415
+
416
+ Ensure the Clarity script is on the host page, or uncomment the injection block in `src/core/Analytics/clarity.ts`.
417
+
418
+ ### Custom Events
419
+
420
+ Listen on the host page:
421
+
422
+ ```js
423
+ window.addEventListener('ikform:analytics', (e) => {
424
+ console.log('IKForm event:', e.detail);
425
+ });
426
+ ```
427
+
428
+ ---
429
+
430
+ ## API Integration
431
+
432
+ All API calls are defined in `ApiContract` objects inside your form config — **no hardcoded fetch calls**.
433
+
434
+ ### Dynamic URL Interpolation
435
+
436
+ ```ts
437
+ endpoint: 'slots/{slot}/reserve'
438
+ // If form data has { slot: "slot-morning" }
439
+ // → POST https://api.example.com/api/slots/slot-morning/reserve
440
+ ```
441
+
442
+ ### Body Mapping
443
+
444
+ ```ts
445
+ bodyMapping: {
446
+ email: 'user.email', // form field → nested request body key
447
+ slot: 'booking.slotId',
448
+ }
449
+ // → { user: { email: "..." }, booking: { slotId: "..." } }
450
+
451
+ // OR forward all fields:
452
+ bodyMapping: '$all'
453
+ ```
454
+
455
+ ### Response Mapping
456
+
457
+ ```ts
458
+ responseMapping: {
459
+ 'data.registrationId': 'registrationId', // response path → form field
460
+ }
461
+ // Writes response.data.registrationId back into form state as "registrationId"
462
+ ```
463
+
464
+ ### Auth Token
465
+
466
+ ```ts
467
+ import { apiClient } from 'ik-embeddable-form';
468
+
469
+ apiClient.setAuthToken('Bearer your-token-here');
470
+ ```
471
+
472
+ ---
473
+
474
+ ## TypeScript Types Reference
475
+
476
+ ```ts
477
+ import type {
478
+ FormConfig,
479
+ FormStep,
480
+ FormField,
481
+ ValidationRule,
482
+ ApiContract,
483
+ AnalyticsEvent,
484
+ OpenConfig,
485
+ ConditionalRule,
486
+ IKFormSDK,
487
+ } from 'ik-embeddable-form';
488
+ ```
489
+
490
+ ---
491
+
492
+ ## License
493
+
494
+ MIT — see [LICENSE](LICENSE)