@chatlychat/widget-element 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # @chatlychat/widget-element — changelog
2
+
3
+ ## 0.1.0 — unreleased
4
+
5
+ - Initial release: `<chatly-widget>` custom element, `defineChatlyWidget()`,
6
+ and the refcounted embed lifecycle (`startWidget`, `controls`) that
7
+ `@chatlychat/widget-react` builds on.
8
+ - Zero dependencies. The main entry has no side effects; `/register` defines
9
+ the tag.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chatly Engineering
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 CHANGED
@@ -1,3 +1,181 @@
1
- # Temporary Holding Version
1
+ # @chatlychat/widget-element
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Chatly chat widget as a custom element. **Zero dependencies.**
4
+
5
+ ```bash
6
+ pnpm add @chatlychat/widget-element
7
+ ```
8
+
9
+ ```html
10
+ <script type="module">
11
+ import '@chatlychat/widget-element/register';
12
+ </script>
13
+
14
+ <chatly-widget public-id="acme-web" api-url="https://api.chatlychat.com"></chatly-widget>
15
+ ```
16
+
17
+ Works in Vue, Svelte, Angular, Lit, Astro, Alpine, htmx and plain HTML —
18
+ anywhere a custom element works. For React, use
19
+ [`@chatlychat/widget-react`](../widget-react), which is built on this package's
20
+ lifecycle.
21
+
22
+ ## Why not just paste the snippet?
23
+
24
+ If a `<script>` in your `<head>` is all you need, paste
25
+ [the snippet](https://chatlychat.com/docs/channel-web) and stop reading. This
26
+ package earns its keep when the widget's lifetime is tied to something your
27
+ framework owns:
28
+
29
+ - the chat should appear on some routes and not others, and your router
30
+ unmounts the element;
31
+ - the theme or the visitor's identity comes from component state;
32
+ - your templates bind properties and listen for events, and you would rather
33
+ write `<chatly-widget :theme="theme" @chatly-ready="…">` than reach into
34
+ `window`.
35
+
36
+ It also handles three things a hand-written integration usually does not: a
37
+ widget left behind when its component unmounts, a re-boot caused by a DOM
38
+ move, and a re-initialisation caused by a re-render that changed nothing.
39
+
40
+ ## Attributes
41
+
42
+ | attribute | notes |
43
+ | ---------------- | ------------------------------------------------------------- |
44
+ | `public-id` | **Required.** The web channel's public ID. |
45
+ | `api-url` | Defaults to the host the loader was served from. |
46
+ | `realtime-url` | Rarely needed; the API reports the right one. |
47
+ | `loader-url` | Where `loader.js` is served from. **Self-hosting: set this.** |
48
+ | `locale` | Applied with `setLocale()` once the widget is up. |
49
+ | `css-overrides` | CSS injected into the widget's shadow root. |
50
+ | `inline` | Boolean. Mount the chat inside this element. |
51
+ | `theme` | JSON. |
52
+ | `behavior` | JSON. |
53
+ | `visitor` | JSON. |
54
+ | `copy` | JSON. |
55
+ | `copy-by-locale` | JSON. |
56
+ | `quick-links` | JSON. |
57
+
58
+ Attributes are live: change one and the element applies it. A changed
59
+ `visitor` becomes an `identify()` and a changed `locale` a `setLocale()` —
60
+ neither re-mounts the widget. A change to anything else re-initialises it,
61
+ which closes an open panel (the widget has no `setTheme`; this is the honest
62
+ cost, and it only happens when a value really changed).
63
+
64
+ ### A malformed JSON attribute does not stop the chat
65
+
66
+ It is dropped, `console.error` says which attribute and why, and the widget
67
+ starts without it.
68
+
69
+ That is the opposite of what the widget does with a `mountTo` selector that
70
+ matches nothing, and deliberately so: a failed `mountTo` would put an
71
+ unexpected floating bubble on a page built to embed the chat, which reads as a
72
+ bug and is easy to miss, whereas a failed `theme` leaves a working chat in the
73
+ default colours and a named error in the console. Losing support chat on your
74
+ site over a trailing comma is the worse trade.
75
+
76
+ ## Properties
77
+
78
+ Every JSON attribute is also a property, and the property wins:
79
+
80
+ ```js
81
+ const chat = document.querySelector('chatly-widget');
82
+ chat.theme = { primaryColor: '#4f46e5', colorScheme: 'auto' };
83
+ chat.visitor = { externalId: 'u_42', email: 'jamie@acme.io', userHash };
84
+ chat.config = { behavior: { tabs: ['home', 'messages'] } }; // the whole object
85
+ ```
86
+
87
+ Setting a property **before** the element is defined or upgraded works too,
88
+ which matters because every property-binding template engine can produce that
89
+ ordering.
90
+
91
+ `chat.controls` is the widget's post-boot method object — `open`, `close`,
92
+ `toggle`, `identify`, `setUser`, `setCustomAttributes`,
93
+ `deleteCustomAttribute`, `setLabel`, `removeLabel`, `setLocale`, `track`,
94
+ `registerPushToken`, `reset` — so a reference to the element is enough to
95
+ drive the chat. It is also exported on its own as `controls`.
96
+
97
+ `init` and `destroy` are deliberately absent: the element owns the lifecycle,
98
+ and calling them behind its back leaves it believing a widget is mounted that
99
+ is not.
100
+
101
+ ## Events
102
+
103
+ The element re-dispatches every widget event as a DOM `CustomEvent`, with the
104
+ widget's payload on `detail`. They bubble and are `composed`, so a listener on
105
+ an ancestor hears them.
106
+
107
+ `chatly-ready`, `chatly-open`, `chatly-close`, `chatly-message`,
108
+ `chatly-conversation`, `chatly-identified`, `chatly-reset`, `chatly-action`,
109
+ `chatly-error`.
110
+
111
+ ```html
112
+ <chatly-widget public-id="acme-web"></chatly-widget>
113
+ <script type="module">
114
+ import '@chatlychat/widget-element/register';
115
+ document.querySelector('chatly-widget').addEventListener('chatly-message', (e) => {
116
+ console.log('reply from', e.detail.authorType);
117
+ });
118
+ </script>
119
+ ```
120
+
121
+ Hyphenated, not `chatly:message`: a colon is legal in an event name but not in
122
+ a Vue, Angular or Svelte template binding.
123
+
124
+ ## Inline mounting
125
+
126
+ ```html
127
+ <chatly-widget public-id="acme-web" inline style="height: 600px"></chatly-widget>
128
+ ```
129
+
130
+ The chat fills the element instead of floating. No launcher, no corner offset,
131
+ and `close()` warns instead of working — hide or remove the element instead.
132
+
133
+ The element sets `display: block` on itself in this mode, because a custom
134
+ element is `display: inline` by default, which collapses to a zero-height box
135
+ and looks exactly like the widget failing to load. It also sets a 420px
136
+ `min-height` unless you set one.
137
+
138
+ ## The main entry has no side effects
139
+
140
+ Importing `@chatlychat/widget-element` defines nothing and touches no DOM.
141
+ `@chatlychat/widget-element/register` is the opt-in that defines
142
+ `<chatly-widget>`, and `defineChatlyWidget('my-tag')` is there if you need a
143
+ different tag name. Calling either twice is safe.
144
+
145
+ This split is why `@chatlychat/widget-react` can depend on this package for the
146
+ embed lifecycle without registering an element its consumers never asked for,
147
+ and why nothing here crashes a server-side bundle — a top-level
148
+ `class X extends HTMLElement` would throw `HTMLElement is not defined` the
149
+ moment a Next.js server component imported so much as a type from here.
150
+
151
+ ## One widget per page
152
+
153
+ `window.Chatly` is a singleton. Two connected `<chatly-widget>` elements with
154
+ different attributes is a misconfiguration: the first owns the configuration,
155
+ the second's attributes are ignored (its events still fire), and a console
156
+ warning says so. Two with the same configuration is harmless — which is what
157
+ makes an element inside a template that re-renders safe.
158
+
159
+ Moving the element in the DOM, or removing and re-adding it in the same task,
160
+ does **not** re-boot the widget. A `disconnectedCallback` is not necessarily
161
+ an unmount: `appendChild` elsewhere fires disconnect then connect, and so does
162
+ a framework re-keying a list.
163
+
164
+ ## Composing with the snippet
165
+
166
+ If the page already has the snippet, this package does not fetch a second
167
+ loader — it queues its `init` onto the shim the snippet installed and lets that
168
+ loader replay it. The element's configuration wins. You do not need both, but
169
+ having both is not broken.
170
+
171
+ ## Self-hosting
172
+
173
+ Set `loader-url`. `GET /v1/channels/:id` returns the right value for your
174
+ deployment as `embed.loaderUrl`; read it from there rather than typing a host.
175
+ The same response lists every origin a running widget connects to
176
+ (`connectOrigins`) — there are three, and a CSP built from the script host
177
+ alone lets the launcher paint and then blocks every request it makes.
178
+
179
+ ## License
180
+
181
+ MIT — see `LICENSE`.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `<chatly-widget>` — the widget as a custom element.
3
+ *
4
+ * ── Why the class is built inside a function ────────────────────────────
5
+ *
6
+ * `class X extends HTMLElement {}` is evaluated when its MODULE is evaluated,
7
+ * and `HTMLElement` does not exist in Node. A top-level class would therefore
8
+ * throw `ReferenceError: HTMLElement is not defined` the moment a Next.js
9
+ * server component, a Remix loader or a Vitest node-environment test imported
10
+ * anything from this package — including just a type. That is not a
11
+ * theoretical risk: it is the single most common way a web-component package
12
+ * breaks an SSR build, and the stack trace points at the framework rather
13
+ * than at us.
14
+ *
15
+ * So the class is created lazily by `defineChatlyWidget()`, the main entry has
16
+ * no side effects at all, and `@chatlychat/widget-element/register` is the
17
+ * opt-in that defines the tag. A React consumer pulling this package in for
18
+ * the embed lifecycle never registers an element they did not ask for.
19
+ *
20
+ * ── Attributes vs properties ────────────────────────────────────────────
21
+ *
22
+ * HTML attributes are strings. `theme`, `behavior`, `visitor`, `copy`,
23
+ * `copy-by-locale` and `quick-links` are objects, so each is accepted BOTH as
24
+ * a JSON attribute (for a page that is really just HTML) and as a property
25
+ * (for a framework template that has a real object to hand). The property
26
+ * wins when both are set, because a framework binding is the more specific
27
+ * statement.
28
+ */
29
+ import { controls } from './embed.js';
30
+ import type { ChatlyBehavior, ChatlyCopy, ChatlyEmbedOptions, ChatlyIdentity, ChatlyQuickLink, ChatlyTheme } from './types.js';
31
+ /** The default tag name. Overridable in `defineChatlyWidget`. */
32
+ export declare const CHATLY_WIDGET_TAG = "chatly-widget";
33
+ /**
34
+ * The events the element re-dispatches, and the handler each comes from.
35
+ *
36
+ * Prefixed `chatly-` rather than `chatly:` — a colon is legal in an event
37
+ * name but not in a Vue/Angular/Svelte template binding, which is most of the
38
+ * audience for a custom element. They bubble and are `composed` so a listener
39
+ * on an ancestor (or outside a shadow root the host page put us in) hears them.
40
+ */
41
+ export declare const ELEMENT_EVENTS: readonly ["ready", "open", "close", "message", "conversation", "identified", "reset", "action", "error"];
42
+ /** `chatly-ready`, `chatly-message`, … — the DOM event names, without prefix. */
43
+ export type ChatlyElementEvent = (typeof ELEMENT_EVENTS)[number];
44
+ /** What `document.querySelector<ChatlyWidgetElement>` gets you. */
45
+ export interface ChatlyWidgetElement extends HTMLElement {
46
+ theme?: ChatlyTheme;
47
+ behavior?: ChatlyBehavior;
48
+ visitor?: ChatlyIdentity;
49
+ copy?: ChatlyCopy;
50
+ copyByLocale?: Record<string, ChatlyCopy>;
51
+ quickLinks?: ChatlyQuickLink[];
52
+ /** The full option object, for a host that would rather set one thing. */
53
+ config?: Partial<ChatlyEmbedOptions>;
54
+ /** `Chatly.open()` and friends, so a reference to the element is enough. */
55
+ readonly controls: typeof controls;
56
+ }
57
+ /**
58
+ * Define the element, once.
59
+ *
60
+ * Idempotent and safe to call from anywhere: a second call with a tag that is
61
+ * already registered returns quietly instead of throwing
62
+ * `NotSupportedError: the name "chatly-widget" has already been used`, which
63
+ * is what two bundles each importing `/register` would otherwise produce.
64
+ */
65
+ export declare function defineChatlyWidget(tagName?: string): void;
66
+ //# sourceMappingURL=element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,QAAQ,EAAuC,MAAM,YAAY,CAAC;AAE3E,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EAEV,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,kBAAkB,CAAC;AAqCjD;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,0GAUjB,CAAC;AAEX,iFAAiF;AACjF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAejE,mEAAmE;AACnE,MAAM,WAAW,mBAAoB,SAAQ,WAAW;IACtD,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,0EAA0E;IAC1E,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,OAAO,QAAQ,CAAC;CACpC;AA+CD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,MAA0B,GAAG,IAAI,CAS5E"}
@@ -0,0 +1,333 @@
1
+ /**
2
+ * `<chatly-widget>` — the widget as a custom element.
3
+ *
4
+ * ── Why the class is built inside a function ────────────────────────────
5
+ *
6
+ * `class X extends HTMLElement {}` is evaluated when its MODULE is evaluated,
7
+ * and `HTMLElement` does not exist in Node. A top-level class would therefore
8
+ * throw `ReferenceError: HTMLElement is not defined` the moment a Next.js
9
+ * server component, a Remix loader or a Vitest node-environment test imported
10
+ * anything from this package — including just a type. That is not a
11
+ * theoretical risk: it is the single most common way a web-component package
12
+ * breaks an SSR build, and the stack trace points at the framework rather
13
+ * than at us.
14
+ *
15
+ * So the class is created lazily by `defineChatlyWidget()`, the main entry has
16
+ * no side effects at all, and `@chatlychat/widget-element/register` is the
17
+ * opt-in that defines the tag. A React consumer pulling this package in for
18
+ * the embed lifecycle never registers an element they did not ask for.
19
+ *
20
+ * ── Attributes vs properties ────────────────────────────────────────────
21
+ *
22
+ * HTML attributes are strings. `theme`, `behavior`, `visitor`, `copy`,
23
+ * `copy-by-locale` and `quick-links` are objects, so each is accepted BOTH as
24
+ * a JSON attribute (for a page that is really just HTML) and as a property
25
+ * (for a framework template that has a real object to hand). The property
26
+ * wins when both are set, because a framework binding is the more specific
27
+ * statement.
28
+ */
29
+ import { controls, startWidget } from './embed.js';
30
+ /** The default tag name. Overridable in `defineChatlyWidget`. */
31
+ export const CHATLY_WIDGET_TAG = 'chatly-widget';
32
+ /**
33
+ * Attribute names, and the option each one sets.
34
+ *
35
+ * Kebab-case on the outside because that is what HTML authors expect and what
36
+ * `attributeChangedCallback` reports; camelCase on the inside because that is
37
+ * the widget's own option name. `json: true` marks the ones parsed rather
38
+ * than passed through.
39
+ */
40
+ const ATTRIBUTES = [
41
+ { attribute: 'public-id', option: 'publicId' },
42
+ { attribute: 'api-url', option: 'apiUrl' },
43
+ { attribute: 'realtime-url', option: 'realtimeUrl' },
44
+ { attribute: 'loader-url', option: 'loaderUrl' },
45
+ { attribute: 'locale', option: 'locale' },
46
+ { attribute: 'css-overrides', option: 'cssOverrides' },
47
+ { attribute: 'theme', option: 'theme', json: true },
48
+ { attribute: 'behavior', option: 'behavior', json: true },
49
+ { attribute: 'visitor', option: 'visitor', json: true },
50
+ { attribute: 'copy', option: 'copy', json: true },
51
+ { attribute: 'copy-by-locale', option: 'copyByLocale', json: true },
52
+ { attribute: 'quick-links', option: 'quickLinks', json: true },
53
+ ];
54
+ /**
55
+ * `inline` is handled separately: it is boolean (present/absent) and it does
56
+ * not carry a value, it decides whether `mountTo` is this element.
57
+ */
58
+ const INLINE_ATTRIBUTE = 'inline';
59
+ const OBSERVED = [...ATTRIBUTES.map((a) => a.attribute), INLINE_ATTRIBUTE];
60
+ /**
61
+ * The events the element re-dispatches, and the handler each comes from.
62
+ *
63
+ * Prefixed `chatly-` rather than `chatly:` — a colon is legal in an event
64
+ * name but not in a Vue/Angular/Svelte template binding, which is most of the
65
+ * audience for a custom element. They bubble and are `composed` so a listener
66
+ * on an ancestor (or outside a shadow root the host page put us in) hears them.
67
+ */
68
+ export const ELEMENT_EVENTS = [
69
+ 'ready',
70
+ 'open',
71
+ 'close',
72
+ 'message',
73
+ 'conversation',
74
+ 'identified',
75
+ 'reset',
76
+ 'action',
77
+ 'error',
78
+ ];
79
+ /**
80
+ * The object-valued properties, for the pre-upgrade dance in the constructor.
81
+ *
82
+ * Derived from `ATTRIBUTES` plus `config` so a new JSON attribute cannot be
83
+ * added without also being rescued on upgrade — the failure mode there is
84
+ * silent, and a list maintained by hand beside another list is how it would
85
+ * happen.
86
+ */
87
+ const PROPERTY_NAMES = [
88
+ ...ATTRIBUTES.filter((a) => a.json === true).map((a) => a.option),
89
+ 'config',
90
+ ];
91
+ /**
92
+ * Parse a JSON attribute, or drop it.
93
+ *
94
+ * ── Why a malformed attribute does not stop the widget ──────────────────
95
+ *
96
+ * A broken `theme` attribute could either (a) refuse to mount, the way the
97
+ * widget refuses a `mountTo` selector that matches nothing, or (b) drop the
98
+ * attribute, say so, and start anyway. (b), because the two cases are not
99
+ * alike: a failed `mountTo` would put an unexpected floating bubble on a page
100
+ * built to embed the chat, which reads as a bug and is easy to miss, whereas a
101
+ * failed `theme` leaves a WORKING chat in the default colours plus a named
102
+ * error in the console. Losing support chat on a customer's site over a
103
+ * trailing comma is the worse trade.
104
+ *
105
+ * ── Why not zod ─────────────────────────────────────────────────────────
106
+ *
107
+ * This package ships to a customer's page with zero dependencies, and zod
108
+ * measured ~11KB gzipped when it reached the widget. There is also nothing
109
+ * for it to be the source of truth ABOUT: the widget's `InitOptions` has no
110
+ * runtime schema on either side of this boundary — it is a typed object
111
+ * literal from the host page — and the server validates what actually reaches
112
+ * it (`widgetInitSchema`). So the boundary asserts what it can honestly
113
+ * assert: this parsed, and it is an object rather than an array or a number.
114
+ */
115
+ function parseJsonAttribute(element, attribute, raw) {
116
+ let parsed;
117
+ try {
118
+ parsed = JSON.parse(raw);
119
+ }
120
+ catch (err) {
121
+ console.error(`[Chatly] <${element.localName}> ignored the ${attribute} attribute — it is not valid ` +
122
+ `JSON. The widget still started, without it. ${String(err)}`);
123
+ return undefined;
124
+ }
125
+ if (typeof parsed !== 'object' || parsed === null) {
126
+ console.error(`[Chatly] <${element.localName}> ignored the ${attribute} attribute — JSON parsed to ` +
127
+ `${typeof parsed}, and this attribute must be an object or array.`);
128
+ return undefined;
129
+ }
130
+ return parsed;
131
+ }
132
+ /**
133
+ * Define the element, once.
134
+ *
135
+ * Idempotent and safe to call from anywhere: a second call with a tag that is
136
+ * already registered returns quietly instead of throwing
137
+ * `NotSupportedError: the name "chatly-widget" has already been used`, which
138
+ * is what two bundles each importing `/register` would otherwise produce.
139
+ */
140
+ export function defineChatlyWidget(tagName = CHATLY_WIDGET_TAG) {
141
+ if (typeof window === 'undefined' || typeof customElements === 'undefined') {
142
+ // SSR, or a DOM-less test. Not an error — see the file header.
143
+ return;
144
+ }
145
+ if (customElements.get(tagName) !== undefined) {
146
+ return;
147
+ }
148
+ customElements.define(tagName, buildElementClass());
149
+ }
150
+ function buildElementClass() {
151
+ return class ChatlyWidget extends HTMLElement {
152
+ static get observedAttributes() {
153
+ return OBSERVED;
154
+ }
155
+ controls = controls;
156
+ /*
157
+ * Object props are held in a private map rather than as class fields.
158
+ *
159
+ * `useDefineForClassFields` is on in this repo's tsconfig, so a declared
160
+ * field is installed with `Object.defineProperty` in the constructor —
161
+ * which would CLOBBER a value a framework set on the element before it was
162
+ * upgraded (`el.theme = {…}` then `document.body.append(el)`). Accessors
163
+ * on the prototype plus a private store keep the pre-upgrade value.
164
+ */
165
+ #props = new Map();
166
+ #handle = null;
167
+ constructor() {
168
+ super();
169
+ /*
170
+ * The pre-upgrade property dance.
171
+ *
172
+ * A framework that does `el.theme = {…}` before the element is defined
173
+ * (or before this module loads) creates an OWN data property, which
174
+ * shadows the prototype accessor for good: the setter never runs, the
175
+ * private store stays empty, and the theme is silently dropped. Every
176
+ * template engine that binds properties — Vue, Angular, Lit, Svelte —
177
+ * can produce that ordering, and so can plain `createElement` +
178
+ * `append` on a page whose bundle is still loading.
179
+ *
180
+ * So on upgrade, each own property is lifted off the instance and
181
+ * re-assigned, which this time reaches the accessor.
182
+ */
183
+ for (const key of PROPERTY_NAMES) {
184
+ if (!Object.prototype.hasOwnProperty.call(this, key)) {
185
+ continue;
186
+ }
187
+ const value = this[key];
188
+ delete this[key];
189
+ this[key] = value;
190
+ }
191
+ }
192
+ get theme() {
193
+ return this.#props.get('theme');
194
+ }
195
+ set theme(value) {
196
+ this.#setProp('theme', value);
197
+ }
198
+ get behavior() {
199
+ return this.#props.get('behavior');
200
+ }
201
+ set behavior(value) {
202
+ this.#setProp('behavior', value);
203
+ }
204
+ get visitor() {
205
+ return this.#props.get('visitor');
206
+ }
207
+ set visitor(value) {
208
+ this.#setProp('visitor', value);
209
+ }
210
+ get copy() {
211
+ return this.#props.get('copy');
212
+ }
213
+ set copy(value) {
214
+ this.#setProp('copy', value);
215
+ }
216
+ get copyByLocale() {
217
+ return this.#props.get('copyByLocale');
218
+ }
219
+ set copyByLocale(value) {
220
+ this.#setProp('copyByLocale', value);
221
+ }
222
+ get quickLinks() {
223
+ return this.#props.get('quickLinks');
224
+ }
225
+ set quickLinks(value) {
226
+ this.#setProp('quickLinks', value);
227
+ }
228
+ get config() {
229
+ return this.#props.get('config');
230
+ }
231
+ set config(value) {
232
+ this.#setProp('config', value);
233
+ }
234
+ #setProp(key, value) {
235
+ if (value === undefined) {
236
+ this.#props.delete(key);
237
+ }
238
+ else {
239
+ this.#props.set(key, value);
240
+ }
241
+ // Only if connected: a property set before the element is in the DOM is
242
+ // configuration, not a change — `connectedCallback` will read it.
243
+ if (this.#handle !== null) {
244
+ this.#handle.update(this.#options(), this.#handlers());
245
+ }
246
+ }
247
+ connectedCallback() {
248
+ if (this.hasAttribute(INLINE_ATTRIBUTE)) {
249
+ /*
250
+ * The element IS the container in inline mode, and the widget fills
251
+ * what it is given. Without `display: block` a custom element is
252
+ * `display: inline`, which collapses to a zero-height box and renders
253
+ * a chat panel nobody can see — the single most reported mistake with
254
+ * inline mounting, and it looks exactly like the widget not loading.
255
+ */
256
+ this.style.display = 'block';
257
+ if (this.style.minHeight === '') {
258
+ this.style.minHeight = '420px';
259
+ }
260
+ }
261
+ this.#handle = startWidget(this, this.#options(), this.#handlers());
262
+ }
263
+ disconnectedCallback() {
264
+ /*
265
+ * A disconnect is not necessarily an unmount: `appendChild` elsewhere in
266
+ * the document fires disconnect then connect, and so does a framework
267
+ * re-keying its list. `startWidget`'s deferred teardown is what makes
268
+ * that survivable — see `release` in embed.ts.
269
+ */
270
+ this.#handle?.stop();
271
+ this.#handle = null;
272
+ }
273
+ attributeChangedCallback() {
274
+ if (this.#handle === null) {
275
+ return;
276
+ }
277
+ this.#handle.update(this.#options(), this.#handlers());
278
+ }
279
+ /** Attributes, then properties (which win), then `config` (which wins). */
280
+ #options() {
281
+ const options = {};
282
+ for (const { attribute, option, json } of ATTRIBUTES) {
283
+ const raw = this.getAttribute(attribute);
284
+ if (raw === null) {
285
+ continue;
286
+ }
287
+ const value = json === true ? parseJsonAttribute(this, attribute, raw) : raw;
288
+ if (value !== undefined) {
289
+ options[option] = value;
290
+ }
291
+ }
292
+ for (const [key, value] of this.#props) {
293
+ if (key !== 'config') {
294
+ options[key] = value;
295
+ }
296
+ }
297
+ Object.assign(options, this.config ?? {});
298
+ if (this.hasAttribute(INLINE_ATTRIBUTE)) {
299
+ options['mountTo'] = this;
300
+ }
301
+ // `publicId` may legitimately be missing here — the element can be in
302
+ // the DOM before its framework has bound the attribute. `startWidget`
303
+ // warns and declines rather than booting a widget with no channel.
304
+ return options;
305
+ }
306
+ /**
307
+ * Re-dispatch every widget event as a DOM event on this element.
308
+ *
309
+ * Written out rather than generated from `ELEMENT_EVENTS` in a loop: the
310
+ * handler signatures differ (`onOpen` takes nothing, `onReady` a session),
311
+ * and assigning through a string index would need a cast that defeats the
312
+ * one thing the compiler is useful for here — noticing that a handler name
313
+ * in `ChatlyEmbedHandlers` has no `chatly-*` event.
314
+ */
315
+ #handlers() {
316
+ const emit = (name) => (detail) => {
317
+ this.dispatchEvent(new CustomEvent(`chatly-${name}`, { detail, bubbles: true, composed: true }));
318
+ };
319
+ return {
320
+ onReady: emit('ready'),
321
+ onOpen: emit('open'),
322
+ onClose: emit('close'),
323
+ onMessage: emit('message'),
324
+ onConversation: emit('conversation'),
325
+ onIdentified: emit('identified'),
326
+ onReset: emit('reset'),
327
+ onAction: emit('action'),
328
+ onError: emit('error'),
329
+ };
330
+ }
331
+ };
332
+ }
333
+ //# sourceMappingURL=element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"element.js","sourceRoot":"","sources":["../src/element.ts"],"names":[],"mappings":"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"}