@nectary/labs 2.3.4 → 2.4.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.
Files changed (35) hide show
  1. package/README.md +354 -0
  2. package/color-select/index.d.ts +39 -0
  3. package/color-select/index.js +96 -0
  4. package/index.d.ts +2 -0
  5. package/index.js +3 -0
  6. package/package.json +2 -6
  7. package/{phone-preview.d.ts → phone-preview/index.d.ts} +18 -7
  8. package/phone-preview/index.js +94 -0
  9. package/phone-preview-rcs-channel/index.d.ts +49 -0
  10. package/phone-preview-rcs-channel/index.js +175 -0
  11. package/phone-preview-rcs-channel-actions/index.d.ts +37 -0
  12. package/phone-preview-rcs-channel-actions/index.js +126 -0
  13. package/phone-preview-rcs-channel-info/index.d.ts +29 -0
  14. package/phone-preview-rcs-channel-info/index.js +64 -0
  15. package/phone-preview-rcs-channel-info-option/index.d.ts +40 -0
  16. package/phone-preview-rcs-channel-info-option/index.js +106 -0
  17. package/phone-preview-rcs-channel-options/index.d.ts +27 -0
  18. package/phone-preview-rcs-channel-options/index.js +46 -0
  19. package/phone-preview-rcs-channel-tabs/index.d.ts +34 -0
  20. package/phone-preview-rcs-channel-tabs/index.js +79 -0
  21. package/{phone-preview-rcs-chat.d.ts → phone-preview-rcs-chat/index.d.ts} +20 -8
  22. package/phone-preview-rcs-chat/index.js +79 -0
  23. package/phone-preview-rcs-chat-message/index.d.ts +35 -0
  24. package/phone-preview-rcs-chat-message/index.js +48 -0
  25. package/utils/element.d.ts +9 -0
  26. package/utils/element.js +35 -0
  27. package/utils/index.d.ts +1 -1
  28. package/utils/index.js +1 -1
  29. package/Readme.md +0 -1
  30. package/color-select.d.ts +0 -34
  31. package/color-select.js +0 -79
  32. package/phone-preview-rcs-channel.d.ts +0 -50
  33. package/phone-preview-rcs-channel.js +0 -319
  34. package/phone-preview-rcs-chat.js +0 -162
  35. package/phone-preview.js +0 -120
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ # Nectary Labs
2
+
3
+ Welcome to Nectary Labs! This is the experimental playground for new components, patterns, and features that are being evaluated for inclusion in the main Nectary design system.
4
+
5
+ ## 🎯 What is Nectary Labs?
6
+
7
+ Nectary Labs is a shared component library where **any team** can contribute their own experimental components. When a component is requested or used by multiple teams, it becomes a candidate for promotion to the main Nectary design system by the official Nectary team.
8
+
9
+ Think of it as an **incubator** where teams can share components with each other, and the most useful ones graduate to become officially supported in the main Nectary repository based on cross-team adoption.
10
+
11
+ ## 🚀 Getting Started
12
+
13
+ ### Development Setup
14
+
15
+ 1. **Clone the repository**
16
+
17
+ ```bash
18
+ git clone <repository-url>
19
+ cd nectary
20
+ ```
21
+
22
+ 2. **Install dependencies**
23
+
24
+ ```bash
25
+ pnpm install
26
+ ```
27
+
28
+ 3. **Start the docs locally**
29
+
30
+ ```bash
31
+ pnpm start
32
+ ```
33
+
34
+ 4. **Create documentation page for testing**
35
+
36
+ Create a documentation page in `docs/latest/src/pages/labComponents/YourComponent/` to display and manually test your component. This is essential for development and validation.
37
+
38
+ ```typescript
39
+ // docs/latest/src/pages/labComponents/YourComponent/examples/Basic.tsx
40
+ import '@nectary/labs/your-component'
41
+
42
+ export const BasicExample = () => (
43
+ <sinch-labs-your-component
44
+ text="Hello World"
45
+ disabled={false}
46
+ />
47
+ )
48
+ ```
49
+
50
+ ## 📝 Creating a New Component
51
+
52
+ ### 1. Component Structure
53
+
54
+ Create a new directory following the naming convention:
55
+
56
+ ```bash
57
+ mkdir my-new-component
58
+ cd my-new-component
59
+ ```
60
+
61
+ ### 2. Create Documentation Page
62
+
63
+ **First**, create a documentation page in `docs/latest/src/pages/labComponents/MyComponent/` to display and manually test your component during development:
64
+
65
+ ```typescript
66
+ // docs/latest/src/pages/labComponents/MyComponent/examples/Basic.tsx
67
+ import '@nectary/labs/my-new-component'
68
+
69
+ export const BasicExample = () => (
70
+ <sinch-labs-my-new-component
71
+ text="Hello World"
72
+ disabled={false}
73
+ />
74
+ )
75
+ ```
76
+
77
+ ### 3. Create the TypeScript File (index.ts)
78
+
79
+ ```typescript
80
+ import { defineCustomElement, NectaryElement } from '../utils'
81
+ import templateHTML from './template.html'
82
+ import type React from 'react'
83
+
84
+ const template = document.createElement('template')
85
+ template.innerHTML = templateHTML
86
+
87
+ export class MyNewComponent extends NectaryElement {
88
+ // Private fields for DOM elements
89
+ #button: HTMLButtonElement
90
+ #controller: AbortController | null = null
91
+
92
+ constructor() {
93
+ super()
94
+
95
+ const shadowRoot = this.attachShadow()
96
+ shadowRoot.appendChild(template.content.cloneNode(true))
97
+
98
+ // Query DOM elements
99
+ this.#button = shadowRoot.querySelector('#button')!
100
+ }
101
+
102
+ connectedCallback() {
103
+ super.connectedCallback()
104
+
105
+ this.#controller = new AbortController()
106
+ const { signal } = this.#controller
107
+
108
+ // Add event listeners
109
+ this.#button.addEventListener('click', this.#onClick, { signal })
110
+
111
+ this.#updateUI()
112
+ }
113
+
114
+ disconnectedCallback() {
115
+ super.disconnectedCallback()
116
+ this.#controller?.abort()
117
+ this.#controller = null
118
+ }
119
+
120
+ static get observedAttributes() {
121
+ return ['disabled', 'text']
122
+ }
123
+
124
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
125
+ if (oldVal === newVal) return
126
+
127
+ switch (name) {
128
+ case 'disabled':
129
+ case 'text':
130
+ this.#updateUI()
131
+ break
132
+ }
133
+ }
134
+
135
+ // Properties with getters/setters
136
+ get disabled(): boolean {
137
+ return this.hasAttribute('disabled')
138
+ }
139
+
140
+ set disabled(value: boolean) {
141
+ if (value) {
142
+ this.setAttribute('disabled', '')
143
+ } else {
144
+ this.removeAttribute('disabled')
145
+ }
146
+ }
147
+
148
+ get text(): string {
149
+ return this.getAttribute('text') ?? ''
150
+ }
151
+
152
+ set text(value: string) {
153
+ this.setAttribute('text', value)
154
+ }
155
+
156
+ #updateUI() {
157
+ if (!this.isDomConnected) return
158
+
159
+ this.#button.disabled = this.disabled
160
+ this.#button.textContent = this.text
161
+ }
162
+
163
+ #onClick = () => {
164
+ this.dispatchEvent(new CustomEvent('-click'))
165
+ }
166
+ }
167
+
168
+ defineCustomElement('sinch-labs-my-new-component', MyNewComponent)
169
+
170
+ // TypeScript definitions
171
+ type Props = {
172
+ disabled?: boolean
173
+ text?: string
174
+ }
175
+
176
+ type ElementProps = Partial<{ [K in keyof Props]: Props[K] | string }>
177
+
178
+ declare global {
179
+ interface HTMLElementTagNameMap {
180
+ 'sinch-labs-my-new-component': ElementProps & HTMLElement
181
+ }
182
+ }
183
+
184
+ declare module 'react' {
185
+ namespace JSX {
186
+ interface IntrinsicElements {
187
+ 'sinch-labs-my-new-component': ElementProps &
188
+ React.ClassAttributes<HTMLElement> &
189
+ React.HTMLAttributes<HTMLElement>
190
+ }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### 4. Create the Template File (template.html)
196
+
197
+ ```html
198
+ <style>
199
+ :host {
200
+ display: inline-block;
201
+ }
202
+
203
+ #button {
204
+ padding: 8px 16px;
205
+ border: 1px solid var(--sinch-sys-color-border-default);
206
+ border-radius: 4px;
207
+ background: var(--sinch-sys-color-surface-default);
208
+ color: var(--sinch-sys-color-text-default);
209
+ font: var(--sinch-sys-font-body-m);
210
+ cursor: pointer;
211
+ }
212
+
213
+ #button:hover {
214
+ background: var(--sinch-sys-color-surface-hover);
215
+ }
216
+
217
+ #button:disabled {
218
+ opacity: 0.5;
219
+ cursor: not-allowed;
220
+ }
221
+ </style>
222
+
223
+ <button id="button" type="button">
224
+ Default Text
225
+ </button>
226
+ ```
227
+
228
+ ## 🎨 Design Patterns
229
+
230
+ ### Use Primitive Props and Slots for Composition
231
+
232
+ Only use primitive types (string, number, boolean) for component properties to keep components as close to native HTML as possible. For complex data structures, prefer slots/children over array props for better composability:
233
+
234
+ ```typescript
235
+ // ❌ Avoid complex props
236
+ <my-component config={{theme: "dark", items: [1, 2, 3]}} />
237
+ <my-component items={[{title: "Item 1"}, {title: "Item 2"}]} />
238
+
239
+ // ✅ Use primitive props and slotted children
240
+ <my-component theme="dark" count="3">
241
+ <my-item title="Item 1" />
242
+ <my-item title="Item 2" />
243
+ </my-component>
244
+ ```
245
+
246
+ ### Event Naming Convention
247
+
248
+ Use the `-` prefix for custom events:
249
+
250
+ ```typescript
251
+ // Dispatch custom events
252
+ this.dispatchEvent(new CustomEvent('-click'))
253
+ this.dispatchEvent(new CustomEvent('-change', { detail: newValue }))
254
+ ```
255
+
256
+ ### Attribute Reflection
257
+
258
+ Always reflect important properties as attributes:
259
+
260
+ ```typescript
261
+ get disabled(): boolean {
262
+ return this.hasAttribute('disabled')
263
+ }
264
+
265
+ set disabled(value: boolean) {
266
+ if (value) {
267
+ this.setAttribute('disabled', '')
268
+ } else {
269
+ this.removeAttribute('disabled')
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Use CSS Custom Properties
275
+
276
+ Leverage design tokens for consistent styling:
277
+
278
+ ```css
279
+ :host {
280
+ color: var(--sinch-sys-color-text-default);
281
+ font: var(--sinch-sys-font-body-m);
282
+ background: var(--sinch-sys-color-surface-default);
283
+ }
284
+ ```
285
+
286
+ ## 🚢 Submission Guidelines
287
+
288
+ ### Commit Messages
289
+
290
+ Follow conventional commit format:
291
+
292
+ ```text
293
+ feat(labs): add new component for data visualization
294
+ fix(labs): resolve accessibility issue in phone preview
295
+ docs(labs): update contributing guide with new patterns
296
+ ```
297
+
298
+ We use [semantic-release](https://semantic-release.gitbook.io/) for automated versioning and publishing. Your commit messages directly determine the version bump:
299
+
300
+ - **`feat:`** triggers a **minor** version bump (e.g., 1.2.0 → 1.3.0)
301
+ - **`fix:`** triggers a **patch** version bump (e.g., 1.2.0 → 1.2.1)
302
+ - **`docs:`**, **`style:`**, **`refactor:`** etc. don't trigger a release
303
+ - **Breaking changes** (with `BREAKING CHANGE:` in footer) trigger a **major** version bump (e.g., 1.2.0 → 2.0.0)
304
+
305
+ This means your commit message format is crucial for proper versioning and release notes generation.
306
+
307
+ ### Pull Request Process
308
+
309
+ 1. **Create Feature Branch**: `git checkout -b feat/my-new-component`
310
+ 2. **Implement Component**: Follow the patterns above
311
+ 3. **Test Thoroughly**: Run build, lint, and manual tests
312
+ 4. **Update Documentation**: Add usage examples
313
+ 5. **Submit PR**: Include clear description and testing notes
314
+
315
+ ### Code Review Criteria
316
+
317
+ - **Architecture**: Follows Nectary Labs patterns
318
+ - **Performance**: Efficient event handling and DOM updates
319
+ - **Accessibility**: Proper ARIA attributes and keyboard support
320
+ - **Design**: Consistent with design system tokens
321
+ - **Documentation**: Clear examples and API documentation
322
+
323
+ ## 🐛 Troubleshooting
324
+
325
+ ### Common Issues
326
+
327
+ **TypeScript Conflicts**: If you see "Subsequent property declarations must have the same type":
328
+
329
+ - Remove any old compiled `.d.ts` files
330
+ - Run `npm run build` to regenerate types
331
+ - Restart TypeScript service in your editor
332
+
333
+ **Import Errors**: Ensure all child components are properly imported:
334
+
335
+ - Check import paths are correct
336
+ - Verify component registration
337
+ - Import child components before parent components
338
+
339
+ **Styling Issues**:
340
+
341
+ - Use `:host` for component root styles
342
+ - Ensure CSS custom properties are defined
343
+ - Check shadow DOM style encapsulation
344
+
345
+ ## 💬 Getting Help
346
+
347
+ - Check existing components in `labs/` for patterns and examples
348
+ - Review the main Nectary design system documentation
349
+ - Ask questions in #nectary channels or discussions
350
+ - Browse the [Lab Components](/labComponents) section for live examples
351
+
352
+ ---
353
+
354
+ **Ready to contribute?** Start by exploring existing components in the repository, then follow the patterns above to create your own experimental component that could benefit teams across the organization!
@@ -0,0 +1,39 @@
1
+ import '@nectary/components/color-menu';
2
+ import '@nectary/components/color-menu-option';
3
+ import '@nectary/components/color-swatch';
4
+ import '@nectary/components/popover';
5
+ import '@nectary/components/select-button';
6
+ import { NectaryElement } from '../utils';
7
+ import type React from 'react';
8
+ export declare class ColorSelect extends NectaryElement {
9
+ #private;
10
+ constructor();
11
+ connectedCallback(): void;
12
+ disconnectedCallback(): void;
13
+ static get observedAttributes(): string[];
14
+ attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null): void;
15
+ get open(): boolean;
16
+ set open(value: boolean);
17
+ get value(): string;
18
+ set value(newValue: string);
19
+ }
20
+ type Props = {
21
+ open?: boolean;
22
+ value?: string;
23
+ };
24
+ type ElementProps = Partial<{
25
+ [K in keyof Props]: Props[K] | string;
26
+ }>;
27
+ declare global {
28
+ interface HTMLElementTagNameMap {
29
+ 'sinch-labs-color-select': ElementProps & HTMLElement;
30
+ }
31
+ }
32
+ declare module 'react' {
33
+ namespace JSX {
34
+ interface IntrinsicElements {
35
+ 'sinch-labs-color-select': ElementProps & React.ClassAttributes<HTMLElement> & React.HTMLAttributes<HTMLElement>;
36
+ }
37
+ }
38
+ }
39
+ export {};
@@ -0,0 +1,96 @@
1
+ import '@nectary/components/color-menu';
2
+ import '@nectary/components/color-menu-option';
3
+ import '@nectary/components/color-swatch';
4
+ import '@nectary/components/popover';
5
+ import '@nectary/components/select-button';
6
+ import { defineCustomElement, NectaryElement } from '../utils';
7
+ import templateHTML from './template.html';
8
+ const template = document.createElement('template');
9
+ template.innerHTML = templateHTML;
10
+ export class ColorSelect extends NectaryElement {
11
+ #popover;
12
+ #selectButton;
13
+ #colorSwatch;
14
+ #colorMenu;
15
+ #controller = null;
16
+ constructor() {
17
+ super();
18
+ const shadowRoot = this.attachShadow();
19
+ shadowRoot.appendChild(template.content.cloneNode(true));
20
+ this.#popover = shadowRoot.querySelector('sinch-popover');
21
+ this.#selectButton = shadowRoot.querySelector('sinch-select-button');
22
+ this.#colorSwatch = shadowRoot.querySelector('sinch-color-swatch');
23
+ this.#colorMenu = shadowRoot.querySelector('sinch-color-menu');
24
+ }
25
+ connectedCallback() {
26
+ super.connectedCallback();
27
+ this.#controller = new AbortController();
28
+ const { signal } = this.#controller;
29
+ this.#selectButton.addEventListener('-click', this.#onOpen, { signal });
30
+ this.#popover.addEventListener('-close', this.#onClose, { signal });
31
+ this.#colorMenu.addEventListener('-change', this.#onChange, { signal });
32
+ this.#updateUI();
33
+ }
34
+ disconnectedCallback() {
35
+ super.disconnectedCallback();
36
+ this.#controller?.abort();
37
+ this.#controller = null;
38
+ }
39
+ static get observedAttributes() {
40
+ return ['open', 'value'];
41
+ }
42
+ attributeChangedCallback(name, oldVal, newVal) {
43
+ if (oldVal === newVal) {
44
+ return;
45
+ }
46
+ switch (name) {
47
+ case 'open':
48
+ case 'value': {
49
+ this.#updateUI();
50
+ break;
51
+ }
52
+ }
53
+ }
54
+ get open() {
55
+ return this.hasAttribute('open');
56
+ }
57
+ set open(value) {
58
+ if (value) {
59
+ this.setAttribute('open', '');
60
+ }
61
+ else {
62
+ this.removeAttribute('open');
63
+ }
64
+ }
65
+ get value() {
66
+ return this.getAttribute('value') ?? '';
67
+ }
68
+ set value(newValue) {
69
+ if (newValue !== '') {
70
+ this.setAttribute('value', newValue);
71
+ }
72
+ else {
73
+ this.removeAttribute('value');
74
+ }
75
+ }
76
+ #updateUI() {
77
+ if (!this.isDomConnected) {
78
+ return;
79
+ }
80
+ this.#popover.toggleAttribute('open', this.open);
81
+ this.#selectButton.setAttribute('text', this.value);
82
+ this.#colorSwatch.setAttribute('name', this.value);
83
+ this.#colorMenu.setAttribute('value', this.value);
84
+ }
85
+ #onClose = () => {
86
+ this.open = false;
87
+ };
88
+ #onOpen = () => {
89
+ this.open = true;
90
+ };
91
+ #onChange = (e) => {
92
+ this.value = e.detail;
93
+ this.dispatchEvent(new CustomEvent('-change', { detail: e.detail }));
94
+ };
95
+ }
96
+ defineCustomElement('sinch-labs-color-select', ColorSelect);
package/index.d.ts CHANGED
@@ -1,2 +1,4 @@
1
+ import './phone-preview-rcs-channel';
2
+ import './phone-preview-rcs-chat-message';
1
3
  declare const _default: {};
2
4
  export default _default;
package/index.js CHANGED
@@ -1,2 +1,5 @@
1
1
  /// Nectary Labs
2
+ // Import to register the phone preview RCS channel web component
3
+ import './phone-preview-rcs-channel';
4
+ import './phone-preview-rcs-chat-message';
2
5
  export default {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/labs",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
@@ -16,15 +16,11 @@
16
16
  "build": "NODE_ENV=production && rimraf lib/ && tsc"
17
17
  },
18
18
  "dependencies": {
19
- "@lit-labs/scoped-registry-mixin": "^1.0.3",
20
- "@nectary/components": "4.10.3",
21
- "solid-element": "1.8.1",
22
- "solid-js": "1.8.19"
19
+ "@nectary/components": "4.10.3"
23
20
  },
24
21
  "devDependencies": {
25
22
  "@types/node": "^18.17.17",
26
23
  "@types/react": "^18.2.21",
27
- "lit": "^3.1.2",
28
24
  "rimraf": "^3.0.2",
29
25
  "ts-node": "^10.9.1",
30
26
  "typescript": "^5.2.2"
@@ -1,3 +1,5 @@
1
+ import { NectaryElement } from '../utils';
2
+ import type React from 'react';
1
3
  /**
2
4
  * Container for channel previews in a styled phone container.
3
5
  * This container uses a custom scaling where the internal elements are scaled to fit the container from a fixed size.
@@ -7,13 +9,22 @@
7
9
  * @param props.clock Clock `Intl.DateTimeFormat` options.
8
10
  * @param props.children Content to display in the phone container.
9
11
  */
10
- declare const PhonePreview: (props: {
11
- locale: string;
12
- clock: Intl.DateTimeFormatOptions;
13
- }, options: {
14
- element: any;
15
- }) => Node | Node[];
16
- type Props = Partial<Parameters<typeof PhonePreview>[0]>;
12
+ export declare class PhonePreview extends NectaryElement {
13
+ #private;
14
+ constructor();
15
+ connectedCallback(): void;
16
+ disconnectedCallback(): void;
17
+ static get observedAttributes(): string[];
18
+ attributeChangedCallback(): void;
19
+ get locale(): string;
20
+ set locale(value: string);
21
+ get clockOptions(): Intl.DateTimeFormatOptions;
22
+ set clockOptions(value: Intl.DateTimeFormatOptions);
23
+ }
24
+ type Props = {
25
+ locale?: string;
26
+ clock?: Intl.DateTimeFormatOptions | string;
27
+ };
17
28
  type ElementProps = Partial<{
18
29
  [K in keyof Props]: Props[K] | string;
19
30
  }>;
@@ -0,0 +1,94 @@
1
+ import { defineCustomElement, NectaryElement } from '../utils';
2
+ import templateHTML from './template.html';
3
+ const template = document.createElement('template');
4
+ template.innerHTML = templateHTML;
5
+ /**
6
+ * Container for channel previews in a styled phone container.
7
+ * This container uses a custom scaling where the internal elements are scaled to fit the container from a fixed size.
8
+ * Because of the fixed size, absolute units (px) are preferred over relative units (rem, em) for the internal elements.
9
+ *
10
+ * @param props.locale Clock locale.
11
+ * @param props.clock Clock `Intl.DateTimeFormat` options.
12
+ * @param props.children Content to display in the phone container.
13
+ */
14
+ export class PhonePreview extends NectaryElement {
15
+ #clockElement;
16
+ #resizeObserver;
17
+ #clockInterval = null;
18
+ #formatter;
19
+ constructor() {
20
+ super();
21
+ const shadowRoot = this.attachShadow();
22
+ shadowRoot.appendChild(template.content.cloneNode(true));
23
+ this.#clockElement = shadowRoot.querySelector('#clock');
24
+ this.#resizeObserver = new ResizeObserver(() => {
25
+ this.#updateScale();
26
+ });
27
+ this.#formatter = new Intl.DateTimeFormat(this.locale, this.clockOptions);
28
+ }
29
+ connectedCallback() {
30
+ super.connectedCallback();
31
+ const section = this.shadowRoot.querySelector('section');
32
+ this.#resizeObserver.observe(this);
33
+ this.#resizeObserver.observe(section);
34
+ this.#startClock();
35
+ this.#updateClock();
36
+ }
37
+ disconnectedCallback() {
38
+ super.disconnectedCallback();
39
+ this.#resizeObserver.disconnect();
40
+ this.#stopClock();
41
+ }
42
+ static get observedAttributes() {
43
+ return ['locale', 'clock'];
44
+ }
45
+ attributeChangedCallback() {
46
+ this.#formatter = new Intl.DateTimeFormat(this.locale, this.clockOptions);
47
+ this.#updateClock();
48
+ }
49
+ get locale() {
50
+ return this.getAttribute('locale') ?? 'en-US';
51
+ }
52
+ set locale(value) {
53
+ this.setAttribute('locale', value);
54
+ }
55
+ get clockOptions() {
56
+ const clockAttr = this.getAttribute('clock');
57
+ if (clockAttr !== null && clockAttr !== '') {
58
+ try {
59
+ return JSON.parse(clockAttr);
60
+ }
61
+ catch {
62
+ // Fallback to default if JSON parsing fails
63
+ }
64
+ }
65
+ return { hour: '2-digit', minute: '2-digit' };
66
+ }
67
+ set clockOptions(value) {
68
+ this.setAttribute('clock', JSON.stringify(value));
69
+ }
70
+ #updateScale() {
71
+ const style = getComputedStyle(this);
72
+ const baseSize = parseFloat(style.getPropertyValue('--base-size'));
73
+ const currentSize = this.getBoundingClientRect().width;
74
+ this.style.setProperty('--scale', `${currentSize / baseSize}`);
75
+ }
76
+ #startClock() {
77
+ this.#stopClock();
78
+ this.#clockInterval = window.setInterval(() => {
79
+ this.#updateClock();
80
+ }, 60000);
81
+ }
82
+ #stopClock() {
83
+ if (this.#clockInterval !== null) {
84
+ clearInterval(this.#clockInterval);
85
+ this.#clockInterval = null;
86
+ }
87
+ }
88
+ #updateClock() {
89
+ if (this.isDomConnected) {
90
+ this.#clockElement.textContent = this.#formatter.format(new Date());
91
+ }
92
+ }
93
+ }
94
+ defineCustomElement('sinch-labs-phone-preview', PhonePreview);