@internetarchive/elements 0.2.2-webdev-8162.0 → 0.2.2-zendesk2

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
@@ -1,5 +1,9 @@
1
1
  # 📚 _elements_ 🏛️
2
2
 
3
+ ## Demo
4
+
5
+ <https://internetarchive.github.io/elements>
6
+
3
7
  ## Installation
4
8
 
5
9
  ```zsh
@@ -16,6 +20,71 @@ import "@internetarchive/elements/ia-button/ia-button";
16
20
  <ia-button @click=() => alert('Clicked!')>Click Me</ia-button>
17
21
  ```
18
22
 
23
+ ## Build Dependencies
24
+
25
+ We use SVGs in this repo, which not all build systems support out of the box. Here's how to support SVGs:
26
+
27
+ ### Webpack
28
+
29
+ Add this to your Webpack config:
30
+
31
+ ```json
32
+ module: {
33
+ rules: [
34
+ {
35
+ test: /\.svg/,
36
+ type: 'asset/resource',
37
+ },
38
+ ],
39
+ },
40
+ ```
41
+
42
+ ### @web/test-runner
43
+
44
+ Install rollup image plugins:
45
+
46
+ ```shell
47
+ npm i -D @rollup/plugin-image @web/dev-server-rollup
48
+ ```
49
+
50
+ Update config:
51
+
52
+ ```js
53
+ import rollupImage from '@rollup/plugin-image';
54
+ import { rollupAdapter } from '@web/dev-server-rollup';
55
+
56
+ export default ({
57
+ mimeTypes: {
58
+ '**/*.scss': 'js',
59
+ '**/*.css': 'js',
60
+ '**/*.svg': 'js',
61
+ '**/*.json': 'js',
62
+ },
63
+ plugins: [rollupAdapter(rollupImage())],
64
+ })
65
+ ```
66
+
67
+ ### Jest
68
+
69
+ Create `SVGMock` file, ie `tests/jest/mocks/svg.js`
70
+
71
+ ```js
72
+ /**
73
+ * Mock for SVG imports in Jest tests.
74
+ */
75
+ export class SVGMock {}
76
+ ```
77
+
78
+ Add to Jest config:
79
+
80
+ ```json
81
+ "jest": {
82
+ "moduleNameMapper": {
83
+ "\\.svg$": "<rootDir>/tests/jest/mocks/svg.js",
84
+ },
85
+ }
86
+ ```
87
+
19
88
  ## Development
20
89
 
21
90
  ```zsh
@@ -23,10 +92,41 @@ npm i
23
92
  npm run dev
24
93
  ```
25
94
 
95
+ ## Versioning and Publishing
96
+
97
+ ### Prerelease Version
98
+
99
+ 1. Create prerelease version on your branch:
100
+ 1. `npm version prerelease --preid=<some_prefix>`
101
+ 2. If you use JIRA, recommend using the ticket number, ie `--preid=webdev-1234`
102
+ 3. This will also create a `git` tag
103
+ 2. Push the tag that was created in the `npm version` step
104
+ 3. Publish prerelease to npm:
105
+ 1. Go to the [Element release page](https://github.com/internetarchive/elements/releases)
106
+ 2. Tap `Draft a new release` button
107
+ 3. Select the tag you created
108
+ 4. Tap `Generate release notes`
109
+ 5. Select `Set as a pre-release`
110
+ 6. Tap `Publish release`
111
+
112
+ ### Release Version
113
+
114
+ 1. Use [Semantic Versioning](https://semver.org) to determine release number
115
+ 2. On the `main` branch:
116
+ 1. Run `npm version [major | minor | patch]`
117
+ 2. `git push && git push --tags`
118
+ 3. Publish release to npm:
119
+ 1. Go to the [Element release page](https://github.com/internetarchive/elements/releases)
120
+ 2. Tap `Draft a new release` button
121
+ 3. Select the tag you created
122
+ 4. Tap `Generate release notes`
123
+ 5. Select `Set as the latest release`
124
+ 6. Tap `Publish release`
125
+
26
126
  ## Adding a Component
27
127
 
28
128
  ### Structure
29
- Each component has its own directory in `src/elements` (or `src/labs` if it's still in development). The basic structure looks like this, though components can have additional files and directories if neededd. Take a look at other elements to see what they each contain.
129
+ Each component has its own directory in `src/elements` (or `src/labs` if it's still in development). The basic structure looks like this, though components can have additional files and directories if needed. Take a look at other elements to see what they each contain.
30
130
  ```
31
131
  src
32
132
  - elements
@@ -42,61 +142,68 @@ To demo your component, we have a component catalog that you can add your demo t
42
142
 
43
143
  We have a story template you can use for consistency. [More info on `story-template`](#story-template). The easiest way to use this is to copy an existing story and modify it for your needs.
44
144
 
45
- Import your story in `/demo/app-root.ts` and add it to the page.
145
+ Your story will be discovered by `/demo/app-root.ts` and added to the page.
46
146
 
47
147
  #### Story Template<a id="story-template"></a>
48
148
 
49
149
  The story template is a component you can use in your story to demo your component. This lets us present them in a consistent way.
50
150
 
51
- It has 5 main configurations:
151
+ It has a few main configurations:
52
152
 
53
153
  *Properties*
54
154
  - `elementTag` (_string_) your component's name, ie `ia-button`
55
- - `exampleUsage` (_string_) your element's example usage, displays it with syntax highlighting
56
155
  - `labs` (_boolean_) if your component is in `labs` to update links
156
+ - `styleInputSettings` (_StyleInputSettings array_) the style options to display, in the appropriate format
157
+ - `propInputSettings` (_PropInputSettings array_) the prop options to display, in the appropriate format
158
+
159
+ **Note:** If the type of prop input you want to use isn't yet available in the `propInputSettings` options,
160
+ you can instead slot in custom property settings by using `slot="settings"`, adding a button to apply the
161
+ properties, and passing in a `customExampleUsage` (see `ia-snow-story.ts` for a sample implementation).
57
162
 
58
163
  *Slots*
59
164
  - `demo` put your component demo in here
60
- - `settings` put your component settings in here
61
165
 
62
166
  Here's an example:
63
167
  ```typescript
64
168
  import '@demo/story-template';
169
+
170
+ // Style options to display
171
+ const styleInputSettings: StyleInputSettings[] = [
172
+ {
173
+ label: 'Button BG color',
174
+ cssVariable: '--button-background-color',
175
+ defaultValue: 'blue', // Should match the actual fallback value for the variable
176
+ inputType: 'color',
177
+ },
178
+ ];
179
+
180
+ // Prop options to display
181
+ const propInputSettings: PropInputSettings[] = [
182
+ {
183
+ label: 'Mode',
184
+ inputType: 'radio',
185
+ propertyName: 'mode', // Should match the name of the property from its @property() declaration
186
+ radioOptions: ['primary', 'secondary', 'danger'],
187
+ defaultValue: 'primary', // Should match the actual default value from the @property() declaration, if any
188
+ },
189
+ ]
65
190
  ...
66
191
  render() {
67
192
  return html`
68
- <story-template elementTag="ia-button" elementClassName="IAButton" .exampleUsage=${this.exampleUsage}>
193
+ <story-template
194
+ elementTag="ia-button"
195
+ elementClassName="IAButton"
196
+ .styleInputData=${{settings: this.styleInputSettings}}
197
+ .propInputData=${{settings: this.propInputSettings}}
198
+ >
69
199
  <div slot="demo">
70
- <ia-button @click=${() => alert('Button clicked!')}
71
- >Click Me</ia-button
72
- >
73
- </div>
74
-
75
- <div slot="settings">
76
- <table>
77
- <tr>
78
- <td>Color</td>
79
- <td><input type="color" value="#007bff" id="color" /></td>
80
- </tr>
81
- </table>
82
- <button @click=${this.apply}>Apply</button>
200
+ <ia-button @click=${() => alert('Button clicked!')}>
201
+ Click Me
202
+ </ia-button>
83
203
  </div>
84
204
  </story-template>
85
205
  `;
86
206
  }
87
-
88
- // return a string of your element's usage and it will
89
- // be displayed with syntax highlighting
90
- // can be dynamic
91
- private get exampleUsage(): string {
92
- return `<ia-button @click=\${() => alert('Button clicked!')}>
93
- Click Me
94
- </ia-button>`;
95
- }
96
-
97
- private apply(): void {
98
- // update component settings based on settings change
99
- }
100
207
  ```
101
208
 
102
209
  ## Component Inventory
@@ -0,0 +1,78 @@
1
+ import { LitElement, type CSSResultGroup, type TemplateResult } from 'lit';
2
+ declare global {
3
+ interface Window {
4
+ /**
5
+ * Zendesk Messenger API injected by the ze-snippet script.
6
+ * Overloaded to cover the two call shapes we use:
7
+ * - `messenger:on` — subscribe to open/close lifecycle events
8
+ * - `messenger` — imperatively open or close the widget panel
9
+ */
10
+ zE?: {
11
+ (target: 'messenger:on', event: 'open' | 'close', callback: () => void): void;
12
+ (target: 'messenger', action: 'open' | 'close'): void;
13
+ };
14
+ }
15
+ }
16
+ /**
17
+ * A lightweight launcher button that loads and opens the Zendesk Messenger
18
+ * widget on demand.
19
+ *
20
+ * The native Zendesk launcher is never shown — this button is the sole entry
21
+ * point. The Zendesk snippet script is fetched lazily on the first click so
22
+ * it does not affect page load performance.
23
+ *
24
+ * @fires zendeskHelpButtonClicked - Dispatched when the user clicks the Help
25
+ * button, before the widget is opened.
26
+ *
27
+ * @cssprop --button-blue - Button background colour (default: `#194880`).
28
+ * @cssprop --icon-fill-color - SVG icon fill colour (default: `#fff`).
29
+ * @cssprop --link-color - Button text colour (default: `#fff`).
30
+ *
31
+ * @example
32
+ * ```html
33
+ * <ia-zendesk-widget
34
+ * widget-src="https://static.zdassets.com/ekr/snippet.js?key=YOUR_KEY"
35
+ * ></ia-zendesk-widget>
36
+ * ```
37
+ */
38
+ export declare class IAZendeskWidget extends LitElement {
39
+ /** URL of the Zendesk `ze-snippet` loader script, including the `key` query param. */
40
+ widgetSrc: string;
41
+ /** Controls Help button visibility. Hidden while the widget panel is open. */
42
+ private buttonVisible;
43
+ /** True from the first click until Zendesk fires the `open` event. Shows the spinner. */
44
+ private isLoading;
45
+ /**
46
+ * Set to `true` after the snippet has loaded and the `messenger:on` listeners
47
+ * have been registered. Prevents duplicate listener registration on
48
+ * subsequent clicks.
49
+ */
50
+ private zendeskReady;
51
+ /**
52
+ * Click handler for the Help button.
53
+ *
54
+ * On the first call: injects the Zendesk script, waits for `window.zE` to
55
+ * become available, registers open/close listeners, then opens the panel.
56
+ *
57
+ * On subsequent calls: the script is already present so we go straight to
58
+ * opening the panel. The spinner stays on until Zendesk fires `'open'`.
59
+ */
60
+ private initiateZenDesk;
61
+ /**
62
+ * Appends the Zendesk snippet `<script>` to `<head>` if it has not already
63
+ * been added. Resolves once the script has loaded; rejects on network error.
64
+ * Guarded by the `ze-snippet` id so multiple widget instances on the same
65
+ * page share a single script tag.
66
+ */
67
+ private loadZendeskScript;
68
+ /**
69
+ * Polls `window.zE` at 100 ms intervals until the Zendesk API is available.
70
+ * The snippet performs its own async initialisation after loading, so
71
+ * `window.zE` may not be set immediately when `script.onload` fires.
72
+ */
73
+ private waitForZendesk;
74
+ private get iconTemplate();
75
+ render(): TemplateResult;
76
+ static get styles(): CSSResultGroup;
77
+ }
78
+ //# sourceMappingURL=ia-zendesk-widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ia-zendesk-widget.d.ts","sourceRoot":"","sources":["../../../../src/elements/ia-zendesk-widget/ia-zendesk-widget.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,KAAK,CAAC;AAQb,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;;;;WAKG;QACH,EAAE,CAAC,EAAE;YACH,CACE,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,MAAM,GAAG,OAAO,EACvB,QAAQ,EAAE,MAAM,IAAI,GACnB,IAAI,CAAC;YACR,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;SACvD,CAAC;KACH;CACF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,sFAAsF;IAEtF,SAAS,SAAM;IAEf,8EAA8E;IACrE,OAAO,CAAC,aAAa,CAAQ;IAEtC,yFAAyF;IAChF,OAAO,CAAC,SAAS,CAAS;IAEnC;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAAS;IAE7B;;;;;;;;OAQG;YACW,eAAe;IAoC7B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAgBtB,OAAO,KAAK,YAAY,GAIvB;IAED,MAAM,IAAI,cAAc;IAYxB,MAAM,KAAK,MAAM,IAAI,cAAc,CAiElC;CACF"}
@@ -0,0 +1,221 @@
1
+ import { __decorate } from "tslib";
2
+ import { css, html, LitElement, } from 'lit';
3
+ import { customElement, property, state } from 'lit/decorators.js';
4
+ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
5
+ import loaderIconSvg from './loader-icon.svg?raw';
6
+ import questionIconSvg from './question-icon.svg?raw';
7
+ /**
8
+ * A lightweight launcher button that loads and opens the Zendesk Messenger
9
+ * widget on demand.
10
+ *
11
+ * The native Zendesk launcher is never shown — this button is the sole entry
12
+ * point. The Zendesk snippet script is fetched lazily on the first click so
13
+ * it does not affect page load performance.
14
+ *
15
+ * @fires zendeskHelpButtonClicked - Dispatched when the user clicks the Help
16
+ * button, before the widget is opened.
17
+ *
18
+ * @cssprop --button-blue - Button background colour (default: `#194880`).
19
+ * @cssprop --icon-fill-color - SVG icon fill colour (default: `#fff`).
20
+ * @cssprop --link-color - Button text colour (default: `#fff`).
21
+ *
22
+ * @example
23
+ * ```html
24
+ * <ia-zendesk-widget
25
+ * widget-src="https://static.zdassets.com/ekr/snippet.js?key=YOUR_KEY"
26
+ * ></ia-zendesk-widget>
27
+ * ```
28
+ */
29
+ let IAZendeskWidget = class IAZendeskWidget extends LitElement {
30
+ constructor() {
31
+ super(...arguments);
32
+ /** URL of the Zendesk `ze-snippet` loader script, including the `key` query param. */
33
+ this.widgetSrc = '';
34
+ /** Controls Help button visibility. Hidden while the widget panel is open. */
35
+ this.buttonVisible = true;
36
+ /** True from the first click until Zendesk fires the `open` event. Shows the spinner. */
37
+ this.isLoading = false;
38
+ /**
39
+ * Set to `true` after the snippet has loaded and the `messenger:on` listeners
40
+ * have been registered. Prevents duplicate listener registration on
41
+ * subsequent clicks.
42
+ */
43
+ this.zendeskReady = false;
44
+ }
45
+ /**
46
+ * Click handler for the Help button.
47
+ *
48
+ * On the first call: injects the Zendesk script, waits for `window.zE` to
49
+ * become available, registers open/close listeners, then opens the panel.
50
+ *
51
+ * On subsequent calls: the script is already present so we go straight to
52
+ * opening the panel. The spinner stays on until Zendesk fires `'open'`.
53
+ */
54
+ async initiateZenDesk() {
55
+ this.isLoading = true;
56
+ this.dispatchEvent(new Event('zendeskHelpButtonClicked'));
57
+ try {
58
+ if (!this.zendeskReady) {
59
+ await this.loadZendeskScript();
60
+ await this.waitForZendesk();
61
+ // Register lifecycle listeners exactly once.
62
+ // isLoading is cleared here (not earlier) so the spinner persists until
63
+ // the widget panel is actually visible to the user.
64
+ window.zE('messenger:on', 'open', () => {
65
+ this.buttonVisible = false;
66
+ this.isLoading = false;
67
+ });
68
+ // Delay matches the Zendesk close animation so the Help button does not
69
+ // reappear while the panel is still sliding out.
70
+ window.zE('messenger:on', 'close', () => {
71
+ setTimeout(() => {
72
+ this.buttonVisible = true;
73
+ }, 500);
74
+ });
75
+ this.zendeskReady = true;
76
+ }
77
+ window.zE('messenger', 'open');
78
+ }
79
+ catch (err) {
80
+ this.isLoading = false;
81
+ // eslint-disable-next-line no-console
82
+ console.error('[ia-zendesk-widget]', err);
83
+ }
84
+ }
85
+ /**
86
+ * Appends the Zendesk snippet `<script>` to `<head>` if it has not already
87
+ * been added. Resolves once the script has loaded; rejects on network error.
88
+ * Guarded by the `ze-snippet` id so multiple widget instances on the same
89
+ * page share a single script tag.
90
+ */
91
+ loadZendeskScript() {
92
+ return new Promise((resolve, reject) => {
93
+ if (document.getElementById('ze-snippet')) {
94
+ resolve();
95
+ return;
96
+ }
97
+ const script = document.createElement('script');
98
+ script.id = 'ze-snippet';
99
+ script.src = this.widgetSrc;
100
+ script.onload = () => resolve();
101
+ script.onerror = () => reject(new Error('Failed to load Zendesk script'));
102
+ document.head.appendChild(script);
103
+ });
104
+ }
105
+ /**
106
+ * Polls `window.zE` at 100 ms intervals until the Zendesk API is available.
107
+ * The snippet performs its own async initialisation after loading, so
108
+ * `window.zE` may not be set immediately when `script.onload` fires.
109
+ */
110
+ waitForZendesk(timeoutMs = 10_000) {
111
+ return new Promise((resolve, reject) => {
112
+ const check = setInterval(() => {
113
+ if (window.zE) {
114
+ clearInterval(check);
115
+ clearTimeout(timeout);
116
+ resolve();
117
+ }
118
+ }, 100);
119
+ const timeout = setTimeout(() => {
120
+ clearInterval(check);
121
+ reject(new Error('Zendesk API did not initialise in time'));
122
+ }, timeoutMs);
123
+ });
124
+ }
125
+ get iconTemplate() {
126
+ return this.isLoading
127
+ ? unsafeHTML(loaderIconSvg)
128
+ : unsafeHTML(questionIconSvg);
129
+ }
130
+ render() {
131
+ return html `
132
+ <button
133
+ class="help-widget ${this.buttonVisible ? '' : 'hidden'}"
134
+ @click=${this.initiateZenDesk}
135
+ >
136
+ ${this.iconTemplate}
137
+ <span class="hidden-sm">Help</span>
138
+ </button>
139
+ `;
140
+ }
141
+ static get styles() {
142
+ return css `
143
+ :host {
144
+ --button-blue: #194880;
145
+ --white: #fff;
146
+ --icon-fill-color: var(--white);
147
+ --link-color: var(--white);
148
+ }
149
+
150
+ .help-widget {
151
+ width: 108px;
152
+ height: 46px;
153
+ margin: 14px 20px;
154
+ position: fixed;
155
+ bottom: 0;
156
+ right: 0;
157
+ z-index: 999998;
158
+ background: var(--button-blue);
159
+ color: var(--link-color);
160
+ letter-spacing: 0.6px;
161
+ font-size: inherit;
162
+ transition: opacity 0.12s linear;
163
+ border-radius: 999rem;
164
+ border: 0;
165
+ outline: none;
166
+ font-weight: 700;
167
+ cursor: pointer;
168
+ vertical-align: middle;
169
+ }
170
+
171
+ .fill-color {
172
+ fill: var(--icon-fill-color);
173
+ }
174
+
175
+ .help-widget svg {
176
+ vertical-align: middle;
177
+ margin-right: 3px;
178
+ pointer-events: none;
179
+ }
180
+
181
+ .help-widget .hidden-sm {
182
+ pointer-events: none;
183
+ }
184
+
185
+ .hidden {
186
+ opacity: 0;
187
+ display: none;
188
+ visibility: hidden;
189
+ }
190
+
191
+ @media (max-width: 767px) {
192
+ .hidden-sm {
193
+ display: none;
194
+ }
195
+
196
+ .help-widget {
197
+ padding: 13px;
198
+ width: initial;
199
+ }
200
+
201
+ .help-widget svg {
202
+ margin-right: 0;
203
+ }
204
+ }
205
+ `;
206
+ }
207
+ };
208
+ __decorate([
209
+ property({ type: String, attribute: 'widget-src' })
210
+ ], IAZendeskWidget.prototype, "widgetSrc", void 0);
211
+ __decorate([
212
+ state()
213
+ ], IAZendeskWidget.prototype, "buttonVisible", void 0);
214
+ __decorate([
215
+ state()
216
+ ], IAZendeskWidget.prototype, "isLoading", void 0);
217
+ IAZendeskWidget = __decorate([
218
+ customElement('ia-zendesk-widget')
219
+ ], IAZendeskWidget);
220
+ export { IAZendeskWidget };
221
+ //# sourceMappingURL=ia-zendesk-widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ia-zendesk-widget.js","sourceRoot":"","sources":["../../../../src/elements/ia-zendesk-widget/ia-zendesk-widget.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n css,\n html,\n LitElement,\n type CSSResultGroup,\n type TemplateResult,\n} from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { unsafeHTML, UnsafeHTMLDirective } from 'lit/directives/unsafe-html.js';\n\nimport loaderIconSvg from './loader-icon.svg?raw';\nimport questionIconSvg from './question-icon.svg?raw';\nimport { DirectiveResult } from 'lit/directive.js';\n\ndeclare global {\n interface Window {\n /**\n * Zendesk Messenger API injected by the ze-snippet script.\n * Overloaded to cover the two call shapes we use:\n * - `messenger:on` — subscribe to open/close lifecycle events\n * - `messenger` — imperatively open or close the widget panel\n */\n zE?: {\n (\n target: 'messenger:on',\n event: 'open' | 'close',\n callback: () => void,\n ): void;\n (target: 'messenger', action: 'open' | 'close'): void;\n };\n }\n}\n\n/**\n * A lightweight launcher button that loads and opens the Zendesk Messenger\n * widget on demand.\n *\n * The native Zendesk launcher is never shown — this button is the sole entry\n * point. The Zendesk snippet script is fetched lazily on the first click so\n * it does not affect page load performance.\n *\n * @fires zendeskHelpButtonClicked - Dispatched when the user clicks the Help\n * button, before the widget is opened.\n *\n * @cssprop --button-blue - Button background colour (default: `#194880`).\n * @cssprop --icon-fill-color - SVG icon fill colour (default: `#fff`).\n * @cssprop --link-color - Button text colour (default: `#fff`).\n *\n * @example\n * ```html\n * <ia-zendesk-widget\n * widget-src=\"https://static.zdassets.com/ekr/snippet.js?key=YOUR_KEY\"\n * ></ia-zendesk-widget>\n * ```\n */\n@customElement('ia-zendesk-widget')\nexport class IAZendeskWidget extends LitElement {\n /** URL of the Zendesk `ze-snippet` loader script, including the `key` query param. */\n @property({ type: String, attribute: 'widget-src' })\n widgetSrc = '';\n\n /** Controls Help button visibility. Hidden while the widget panel is open. */\n @state() private buttonVisible = true;\n\n /** True from the first click until Zendesk fires the `open` event. Shows the spinner. */\n @state() private isLoading = false;\n\n /**\n * Set to `true` after the snippet has loaded and the `messenger:on` listeners\n * have been registered. Prevents duplicate listener registration on\n * subsequent clicks.\n */\n private zendeskReady = false;\n\n /**\n * Click handler for the Help button.\n *\n * On the first call: injects the Zendesk script, waits for `window.zE` to\n * become available, registers open/close listeners, then opens the panel.\n *\n * On subsequent calls: the script is already present so we go straight to\n * opening the panel. The spinner stays on until Zendesk fires `'open'`.\n */\n private async initiateZenDesk(): Promise<void> {\n this.isLoading = true;\n this.dispatchEvent(new Event('zendeskHelpButtonClicked'));\n\n try {\n if (!this.zendeskReady) {\n await this.loadZendeskScript();\n await this.waitForZendesk();\n\n // Register lifecycle listeners exactly once.\n // isLoading is cleared here (not earlier) so the spinner persists until\n // the widget panel is actually visible to the user.\n window.zE!('messenger:on', 'open', () => {\n this.buttonVisible = false;\n this.isLoading = false;\n });\n\n // Delay matches the Zendesk close animation so the Help button does not\n // reappear while the panel is still sliding out.\n window.zE!('messenger:on', 'close', () => {\n setTimeout(() => {\n this.buttonVisible = true;\n }, 500);\n });\n\n this.zendeskReady = true;\n }\n\n window.zE!('messenger', 'open');\n } catch (err) {\n this.isLoading = false;\n // eslint-disable-next-line no-console\n console.error('[ia-zendesk-widget]', err);\n }\n }\n\n /**\n * Appends the Zendesk snippet `<script>` to `<head>` if it has not already\n * been added. Resolves once the script has loaded; rejects on network error.\n * Guarded by the `ze-snippet` id so multiple widget instances on the same\n * page share a single script tag.\n */\n private loadZendeskScript(): Promise<void> {\n return new Promise((resolve, reject) => {\n if (document.getElementById('ze-snippet')) {\n resolve();\n return;\n }\n const script = document.createElement('script');\n script.id = 'ze-snippet';\n script.src = this.widgetSrc;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('Failed to load Zendesk script'));\n document.head.appendChild(script);\n });\n }\n\n /**\n * Polls `window.zE` at 100 ms intervals until the Zendesk API is available.\n * The snippet performs its own async initialisation after loading, so\n * `window.zE` may not be set immediately when `script.onload` fires.\n */\n private waitForZendesk(timeoutMs = 10_000): Promise<void> {\n return new Promise((resolve, reject) => {\n const check = setInterval(() => {\n if (window.zE) {\n clearInterval(check);\n clearTimeout(timeout);\n resolve();\n }\n }, 100);\n const timeout = setTimeout(() => {\n clearInterval(check);\n reject(new Error('Zendesk API did not initialise in time'));\n }, timeoutMs);\n });\n }\n\n private get iconTemplate(): DirectiveResult<typeof UnsafeHTMLDirective> {\n return this.isLoading\n ? unsafeHTML(loaderIconSvg)\n : unsafeHTML(questionIconSvg);\n }\n\n render(): TemplateResult {\n return html`\n <button\n class=\"help-widget ${this.buttonVisible ? '' : 'hidden'}\"\n @click=${this.initiateZenDesk}\n >\n ${this.iconTemplate}\n <span class=\"hidden-sm\">Help</span>\n </button>\n `;\n }\n\n static get styles(): CSSResultGroup {\n return css`\n :host {\n --button-blue: #194880;\n --white: #fff;\n --icon-fill-color: var(--white);\n --link-color: var(--white);\n }\n\n .help-widget {\n width: 108px;\n height: 46px;\n margin: 14px 20px;\n position: fixed;\n bottom: 0;\n right: 0;\n z-index: 999998;\n background: var(--button-blue);\n color: var(--link-color);\n letter-spacing: 0.6px;\n font-size: inherit;\n transition: opacity 0.12s linear;\n border-radius: 999rem;\n border: 0;\n outline: none;\n font-weight: 700;\n cursor: pointer;\n vertical-align: middle;\n }\n\n .fill-color {\n fill: var(--icon-fill-color);\n }\n\n .help-widget svg {\n vertical-align: middle;\n margin-right: 3px;\n pointer-events: none;\n }\n\n .help-widget .hidden-sm {\n pointer-events: none;\n }\n\n .hidden {\n opacity: 0;\n display: none;\n visibility: hidden;\n }\n\n @media (max-width: 767px) {\n .hidden-sm {\n display: none;\n }\n\n .help-widget {\n padding: 13px;\n width: initial;\n }\n\n .help-widget svg {\n margin-right: 0;\n }\n }\n `;\n }\n}\n"]}
@@ -0,0 +1,26 @@
1
+ <svg
2
+ class="icon-loader"
3
+ height="20"
4
+ viewBox="0 0 128 128"
5
+ width="20"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ >
8
+ <g>
9
+ <circle cx="16" cy="64" r="16" class="fill-color" fill-opacity="1" />
10
+ <circle cx="16" cy="64" r="14.344" class="fill-color" fill-opacity="1" transform="rotate(45 64 64)" />
11
+ <circle cx="16" cy="64" r="12.531" class="fill-color" fill-opacity="1" transform="rotate(90 64 64)" />
12
+ <circle cx="16" cy="64" r="10.75" class="fill-color" fill-opacity="1" transform="rotate(135 64 64)" />
13
+ <circle cx="16" cy="64" r="10.063" class="fill-color" fill-opacity="1" transform="rotate(180 64 64)" />
14
+ <circle cx="16" cy="64" r="8.063" class="fill-color" fill-opacity="1" transform="rotate(225 64 64)" />
15
+ <circle cx="16" cy="64" r="6.438" class="fill-color" fill-opacity="1" transform="rotate(270 64 64)" />
16
+ <circle cx="16" cy="64" r="5.375" class="fill-color" fill-opacity="1" transform="rotate(315 64 64)" />
17
+ <animateTransform
18
+ attributeName="transform"
19
+ type="rotate"
20
+ values="0 64 64;315 64 64;270 64 64;225 64 64;180 64 64;135 64 64;90 64 64;45 64 64"
21
+ calcMode="discrete"
22
+ dur="720ms"
23
+ repeatCount="indefinite"
24
+ />
25
+ </g>
26
+ </svg>
@@ -0,0 +1,27 @@
1
+ <svg
2
+ class="icon-question"
3
+ height="20"
4
+ viewBox="0 0 20 20"
5
+ width="20"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ >
8
+ <g class="fill-color" fill-rule="nonzero">
9
+ <path
10
+ d="m10.7078125 12.8359375v-.4140625c0-.765625.2890625-1.1484375 1.328125-1.75
11
+ 1.1015625-.6484375 1.671875-1.46875 1.671875-2.6484375
12
+ 0-1.78125-1.4765625-3.0234375-3.7109375-3.0234375
13
+ -2.3984375 0-3.7578125 1.34375-3.796875 3.265625h2.1171875
14
+ c.046875-.8828125.640625-1.453125 1.5390625-1.453125
15
+ .890625 0 1.484375.5390625 1.484375 1.28125s-.3046875 1.125-1.3125 1.7265625
16
+ c-1.078125.6328125-1.5078125 1.3359375-1.40625 2.5625l.015625.453125z"
17
+ />
18
+ <path
19
+ d="m9.7 16.57c.711 0 1.281-.539 1.281-1.227s-.57-1.226-1.28-1.226
20
+ c-.704 0-1.274.539-1.274 1.226s.57 1.227 1.274 1.227z"
21
+ />
22
+ <path
23
+ d="m10 0c-5.523 0-10 4.477-10 10s4.477 10 10 10 10-4.477 10-10-4.477-10-10-10z
24
+ m0 2c4.418 0 8 3.582 8 8s-3.582 8-8 8-8-3.582-8-8 3.582-8 8-8z"
25
+ />
26
+ </g>
27
+ </svg>
@@ -1,3 +1,4 @@
1
1
  export * from './ia-button/ia-button.js';
2
2
  export * from './ia-combo-box/ia-combo-box.js';
3
+ export * from './ia-zendesk-widget/ia-zendesk-widget.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC"}
@@ -1,3 +1,4 @@
1
1
  export * from './ia-button/ia-button.js';
2
2
  export * from './ia-combo-box/ia-combo-box.js';
3
+ export * from './ia-zendesk-widget/ia-zendesk-widget.js';
3
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC","sourcesContent":["export * from './ia-button/ia-button';\nexport * from './ia-combo-box/ia-combo-box';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/elements/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uCAAuC,CAAC","sourcesContent":["export * from './ia-button/ia-button';\nexport * from './ia-combo-box/ia-combo-box';\nexport * from './ia-zendesk-widget/ia-zendesk-widget';\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@internetarchive/elements",
3
- "version": "0.2.2-webdev-8162.0",
3
+ "version": "0.2.2-zendesk2",
4
4
  "description": "A web component library from the Internet Archive.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "types": "./dist/src/elements/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@lit/localize": "^0.12.2",
31
- "lit": "^2.7.0 || ^3.0.0",
31
+ "lit": "^2.8.0 || ^3.3.2",
32
32
  "magic-snowflakes": "^7.0.2",
33
33
  "tslib": "^2.8.1"
34
34
  },