@lukso/core 0.1.0-dev.0f1bea5
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/LICENSE +201 -0
- package/README.md +159 -0
- package/dist/chunk-3WGYJTN4.js +19 -0
- package/dist/chunk-3WGYJTN4.js.map +1 -0
- package/dist/chunk-4TNWG4ME.js +106 -0
- package/dist/chunk-4TNWG4ME.js.map +1 -0
- package/dist/chunk-AMRGSLR5.cjs +1 -0
- package/dist/chunk-AMRGSLR5.cjs.map +1 -0
- package/dist/chunk-CC3LFUYY.cjs +19 -0
- package/dist/chunk-CC3LFUYY.cjs.map +1 -0
- package/dist/chunk-DFMMMF62.cjs +1 -0
- package/dist/chunk-DFMMMF62.cjs.map +1 -0
- package/dist/chunk-DKEXQFNE.js +1 -0
- package/dist/chunk-DKEXQFNE.js.map +1 -0
- package/dist/chunk-DKXHVRHM.js +84 -0
- package/dist/chunk-DKXHVRHM.js.map +1 -0
- package/dist/chunk-FR74YPGJ.cjs +87 -0
- package/dist/chunk-FR74YPGJ.cjs.map +1 -0
- package/dist/chunk-LEL6VWU4.js +1 -0
- package/dist/chunk-LEL6VWU4.js.map +1 -0
- package/dist/chunk-MBIRTPNM.cjs +84 -0
- package/dist/chunk-MBIRTPNM.cjs.map +1 -0
- package/dist/chunk-NJQVWIZL.cjs +49 -0
- package/dist/chunk-NJQVWIZL.cjs.map +1 -0
- package/dist/chunk-RM42NG7E.cjs +106 -0
- package/dist/chunk-RM42NG7E.cjs.map +1 -0
- package/dist/chunk-SV4TVR2K.js +87 -0
- package/dist/chunk-SV4TVR2K.js.map +1 -0
- package/dist/chunk-X2QNFZU7.js +49 -0
- package/dist/chunk-X2QNFZU7.js.map +1 -0
- package/dist/index.cjs +37 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/mixins/device.cjs +8 -0
- package/dist/mixins/device.cjs.map +1 -0
- package/dist/mixins/device.d.cts +34 -0
- package/dist/mixins/device.d.ts +34 -0
- package/dist/mixins/device.js +8 -0
- package/dist/mixins/device.js.map +1 -0
- package/dist/mixins/index.cjs +14 -0
- package/dist/mixins/index.cjs.map +1 -0
- package/dist/mixins/index.d.cts +3 -0
- package/dist/mixins/index.d.ts +3 -0
- package/dist/mixins/index.js +14 -0
- package/dist/mixins/index.js.map +1 -0
- package/dist/mixins/intl.cjs +8 -0
- package/dist/mixins/intl.cjs.map +1 -0
- package/dist/mixins/intl.d.cts +37 -0
- package/dist/mixins/intl.d.ts +37 -0
- package/dist/mixins/intl.js +8 -0
- package/dist/mixins/intl.js.map +1 -0
- package/dist/services/device.cjs +7 -0
- package/dist/services/device.cjs.map +1 -0
- package/dist/services/device.d.cts +71 -0
- package/dist/services/device.d.ts +71 -0
- package/dist/services/device.js +7 -0
- package/dist/services/device.js.map +1 -0
- package/dist/services/index.cjs +20 -0
- package/dist/services/index.cjs.map +1 -0
- package/dist/services/index.d.cts +4 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/index.js +20 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/intl.cjs +15 -0
- package/dist/services/intl.cjs.map +1 -0
- package/dist/services/intl.d.cts +170 -0
- package/dist/services/intl.d.ts +170 -0
- package/dist/services/intl.js +15 -0
- package/dist/services/intl.js.map +1 -0
- package/dist/utils/index.cjs +11 -0
- package/dist/utils/index.cjs.map +1 -0
- package/dist/utils/index.d.cts +32 -0
- package/dist/utils/index.d.ts +32 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +114 -0
- package/src/index.ts +10 -0
- package/src/mixins/__tests__/device.spec.ts +119 -0
- package/src/mixins/__tests__/intl.spec.ts +198 -0
- package/src/mixins/device.ts +48 -0
- package/src/mixins/index.ts +8 -0
- package/src/mixins/intl.ts +112 -0
- package/src/services/__tests__/device.spec.ts +36 -0
- package/src/services/__tests__/intl.spec.ts +536 -0
- package/src/services/device.ts +121 -0
- package/src/services/index.ts +17 -0
- package/src/services/intl.ts +326 -0
- package/src/utils/__tests__/browserInfo.spec.ts +310 -0
- package/src/utils/__tests__/slug.spec.ts +26 -0
- package/src/utils/browserInfo.ts +102 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/slug.ts +13 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { withDeviceService } from '../device'
|
|
4
|
+
|
|
5
|
+
describe('withDeviceService Mixin', () => {
|
|
6
|
+
let element: any
|
|
7
|
+
let TestComponent: any
|
|
8
|
+
let tagName: string
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
// Create a unique tag name for each test to avoid registration conflicts
|
|
12
|
+
tagName = `test-device-${Math.random().toString(36).slice(2)}`
|
|
13
|
+
|
|
14
|
+
// Create a test component using the mixin
|
|
15
|
+
TestComponent = class extends withDeviceService(LitElement) {
|
|
16
|
+
render() {
|
|
17
|
+
return html`<div>${this.device?.isMobile ? 'mobile' : 'desktop'}</div>`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Register the component before instantiation
|
|
22
|
+
if (!customElements.get(tagName)) {
|
|
23
|
+
customElements.define(tagName, TestComponent)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
element = new TestComponent()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
if (element?.parentElement) {
|
|
31
|
+
element.remove()
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should initialize device service when connected', async () => {
|
|
36
|
+
document.body.appendChild(element)
|
|
37
|
+
await element.updateComplete
|
|
38
|
+
|
|
39
|
+
expect(element.device).toBeDefined()
|
|
40
|
+
expect(typeof element.device).toBe('object')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should have device detection methods', async () => {
|
|
44
|
+
document.body.appendChild(element)
|
|
45
|
+
await element.updateComplete
|
|
46
|
+
|
|
47
|
+
expect(element.device).toHaveProperty('isMobile')
|
|
48
|
+
expect(element.device).toHaveProperty('isTablet')
|
|
49
|
+
expect(element.device).toHaveProperty('isDesktop')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should have OS detection properties', async () => {
|
|
53
|
+
document.body.appendChild(element)
|
|
54
|
+
await element.updateComplete
|
|
55
|
+
|
|
56
|
+
expect(element.device).toHaveProperty('isIOS')
|
|
57
|
+
expect(element.device).toHaveProperty('isAndroid')
|
|
58
|
+
expect(element.device).toHaveProperty('isWindows')
|
|
59
|
+
expect(element.device).toHaveProperty('isMacOS')
|
|
60
|
+
expect(element.device).toHaveProperty('isLinux')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('should have browser detection properties', async () => {
|
|
64
|
+
document.body.appendChild(element)
|
|
65
|
+
await element.updateComplete
|
|
66
|
+
|
|
67
|
+
expect(element.device).toHaveProperty('isChrome')
|
|
68
|
+
expect(element.device).toHaveProperty('isSafari')
|
|
69
|
+
expect(element.device).toHaveProperty('isFirefox')
|
|
70
|
+
expect(element.device).toHaveProperty('isEdge')
|
|
71
|
+
expect(element.device).toHaveProperty('isOpera')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should have getRaw method', async () => {
|
|
75
|
+
document.body.appendChild(element)
|
|
76
|
+
await element.updateComplete
|
|
77
|
+
|
|
78
|
+
expect(element.device).toHaveProperty('getRaw')
|
|
79
|
+
expect(typeof element.device.getRaw).toBe('function')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should provide device detection values as booleans', async () => {
|
|
83
|
+
document.body.appendChild(element)
|
|
84
|
+
await element.updateComplete
|
|
85
|
+
|
|
86
|
+
expect(typeof element.device.isMobile).toBe('boolean')
|
|
87
|
+
expect(typeof element.device.isDesktop).toBe('boolean')
|
|
88
|
+
expect(typeof element.device.isChrome).toBe('boolean')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should work with multiple component instances', async () => {
|
|
92
|
+
const element2 = new TestComponent()
|
|
93
|
+
|
|
94
|
+
document.body.appendChild(element)
|
|
95
|
+
document.body.appendChild(element2)
|
|
96
|
+
await element.updateComplete
|
|
97
|
+
await element2.updateComplete
|
|
98
|
+
|
|
99
|
+
expect(element.device).toBeDefined()
|
|
100
|
+
expect(element2.device).toBeDefined()
|
|
101
|
+
// Both instances should have the same device data
|
|
102
|
+
expect(element.device.isMobile).toEqual(element2.device.isMobile)
|
|
103
|
+
|
|
104
|
+
element2.remove()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should maintain device property after disconnection', async () => {
|
|
108
|
+
document.body.appendChild(element)
|
|
109
|
+
await element.updateComplete
|
|
110
|
+
|
|
111
|
+
const deviceBefore = element.device
|
|
112
|
+
element.remove()
|
|
113
|
+
const deviceAfter = element.device
|
|
114
|
+
|
|
115
|
+
// Device reference should still exist after disconnection
|
|
116
|
+
expect(deviceAfter).toBeDefined()
|
|
117
|
+
expect(deviceAfter).toEqual(deviceBefore)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit'
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import {
|
|
4
|
+
clearIntlService,
|
|
5
|
+
createIntlService,
|
|
6
|
+
setIntlService,
|
|
7
|
+
} from '../../services/intl'
|
|
8
|
+
import { withIntlService } from '../intl'
|
|
9
|
+
|
|
10
|
+
describe('withIntlService Mixin', () => {
|
|
11
|
+
let element: any
|
|
12
|
+
let TestComponent: any
|
|
13
|
+
let tagName: string
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
clearIntlService()
|
|
17
|
+
|
|
18
|
+
// Create a unique tag name for each test to avoid registration conflicts
|
|
19
|
+
tagName = `test-intl-${Math.random().toString(36).slice(2)}`
|
|
20
|
+
|
|
21
|
+
// Create a test component using the mixin
|
|
22
|
+
TestComponent = class extends withIntlService(LitElement) {
|
|
23
|
+
render() {
|
|
24
|
+
return html`<div>${this.formatMessage('greeting')}</div>`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Register the component before instantiation
|
|
29
|
+
if (!customElements.get(tagName)) {
|
|
30
|
+
customElements.define(tagName, TestComponent)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
element = new TestComponent()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
if (element?.parentElement) {
|
|
38
|
+
element.remove()
|
|
39
|
+
}
|
|
40
|
+
clearIntlService()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should add formatMessage method to component', async () => {
|
|
44
|
+
document.body.appendChild(element)
|
|
45
|
+
await element.updateComplete
|
|
46
|
+
|
|
47
|
+
expect(element).toHaveProperty('formatMessage')
|
|
48
|
+
expect(typeof element.formatMessage).toBe('function')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should use global intl service when available', async () => {
|
|
52
|
+
const intl = createIntlService({
|
|
53
|
+
locale: 'en-US',
|
|
54
|
+
messages: { greeting: 'Hello' },
|
|
55
|
+
})
|
|
56
|
+
setIntlService(intl)
|
|
57
|
+
|
|
58
|
+
document.body.appendChild(element)
|
|
59
|
+
await element.updateComplete
|
|
60
|
+
|
|
61
|
+
const result = element.formatMessage('greeting')
|
|
62
|
+
expect(result).toBe('Hello')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should create fallback behavior if global service not available', async () => {
|
|
66
|
+
clearIntlService()
|
|
67
|
+
|
|
68
|
+
document.body.appendChild(element)
|
|
69
|
+
await element.updateComplete
|
|
70
|
+
|
|
71
|
+
// Should return key as fallback
|
|
72
|
+
const result = element.formatMessage('any_key')
|
|
73
|
+
expect(result).toBe('any_key')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should format messages with variables', async () => {
|
|
77
|
+
const intl = createIntlService({
|
|
78
|
+
locale: 'en-US',
|
|
79
|
+
messages: {
|
|
80
|
+
welcome: 'Welcome, {name}!',
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
setIntlService(intl)
|
|
84
|
+
|
|
85
|
+
document.body.appendChild(element)
|
|
86
|
+
await element.updateComplete
|
|
87
|
+
|
|
88
|
+
const result = element.formatMessage('welcome', { name: 'John' })
|
|
89
|
+
expect(result).toContain('Welcome')
|
|
90
|
+
expect(result).toContain('John')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should return key as fallback for missing messages', async () => {
|
|
94
|
+
const intl = createIntlService({
|
|
95
|
+
locale: 'en-US',
|
|
96
|
+
messages: {},
|
|
97
|
+
})
|
|
98
|
+
setIntlService(intl)
|
|
99
|
+
|
|
100
|
+
document.body.appendChild(element)
|
|
101
|
+
await element.updateComplete
|
|
102
|
+
|
|
103
|
+
const result = element.formatMessage('missing_key')
|
|
104
|
+
expect(result).toBe('missing_key')
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('should reflect locale changes in formatting', async () => {
|
|
108
|
+
const intl = createIntlService({
|
|
109
|
+
locale: 'en-US',
|
|
110
|
+
messages: { greeting: 'Hello' },
|
|
111
|
+
})
|
|
112
|
+
setIntlService(intl)
|
|
113
|
+
|
|
114
|
+
document.body.appendChild(element)
|
|
115
|
+
await element.updateComplete
|
|
116
|
+
|
|
117
|
+
expect(element.formatMessage('greeting')).toBe('Hello')
|
|
118
|
+
|
|
119
|
+
intl.setLocale('de-DE', { greeting: 'Hallo' })
|
|
120
|
+
await element.updateComplete
|
|
121
|
+
|
|
122
|
+
expect(element.formatMessage('greeting')).toBe('Hallo')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should work with multiple component instances using same global service', async () => {
|
|
126
|
+
const intl = createIntlService({
|
|
127
|
+
locale: 'en-US',
|
|
128
|
+
messages: { greeting: 'Hello' },
|
|
129
|
+
})
|
|
130
|
+
setIntlService(intl)
|
|
131
|
+
|
|
132
|
+
const element2 = new TestComponent()
|
|
133
|
+
|
|
134
|
+
document.body.appendChild(element)
|
|
135
|
+
document.body.appendChild(element2)
|
|
136
|
+
await element.updateComplete
|
|
137
|
+
await element2.updateComplete
|
|
138
|
+
|
|
139
|
+
expect(element.formatMessage('greeting')).toBe('Hello')
|
|
140
|
+
expect(element2.formatMessage('greeting')).toBe('Hello')
|
|
141
|
+
|
|
142
|
+
element2.remove()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should handle locale changes without errors', async () => {
|
|
146
|
+
const intl = createIntlService({
|
|
147
|
+
locale: 'en-US',
|
|
148
|
+
messages: { greeting: 'Hello' },
|
|
149
|
+
})
|
|
150
|
+
setIntlService(intl)
|
|
151
|
+
|
|
152
|
+
document.body.appendChild(element)
|
|
153
|
+
await element.updateComplete
|
|
154
|
+
|
|
155
|
+
// Rapidly change locales - should not throw
|
|
156
|
+
expect(() => {
|
|
157
|
+
intl.setLocale('de-DE', { greeting: 'Hallo' })
|
|
158
|
+
intl.setLocale('fr-FR', { greeting: 'Bonjour' })
|
|
159
|
+
intl.setLocale('es-ES', { greeting: 'Hola' })
|
|
160
|
+
}).not.toThrow()
|
|
161
|
+
|
|
162
|
+
expect(element.formatMessage('greeting')).toBe('Hola')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should handle undefined key gracefully', async () => {
|
|
166
|
+
const intl = createIntlService({
|
|
167
|
+
locale: 'en-US',
|
|
168
|
+
messages: {},
|
|
169
|
+
})
|
|
170
|
+
setIntlService(intl)
|
|
171
|
+
|
|
172
|
+
document.body.appendChild(element)
|
|
173
|
+
await element.updateComplete
|
|
174
|
+
|
|
175
|
+
// Should not throw with undefined key
|
|
176
|
+
expect(() => {
|
|
177
|
+
element.formatMessage(undefined)
|
|
178
|
+
}).not.toThrow()
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should work after component disconnection', async () => {
|
|
182
|
+
const intl = createIntlService({
|
|
183
|
+
locale: 'en-US',
|
|
184
|
+
messages: { greeting: 'Hello' },
|
|
185
|
+
})
|
|
186
|
+
setIntlService(intl)
|
|
187
|
+
|
|
188
|
+
document.body.appendChild(element)
|
|
189
|
+
await element.updateComplete
|
|
190
|
+
|
|
191
|
+
element.remove()
|
|
192
|
+
|
|
193
|
+
// Locale changes should still work without errors
|
|
194
|
+
expect(() => {
|
|
195
|
+
intl.setLocale('de-DE', { greeting: 'Hallo' })
|
|
196
|
+
}).not.toThrow()
|
|
197
|
+
})
|
|
198
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Detection Mixin
|
|
3
|
+
*
|
|
4
|
+
* Mixin to add device detection service to a Lit component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { LitElement } from 'lit'
|
|
8
|
+
import type { DeviceService } from '../services/device.js'
|
|
9
|
+
import { deviceService, type NavigatorExtended } from '../services/device.js'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mixin to add device detection service to a Lit component
|
|
13
|
+
*
|
|
14
|
+
* Provides a `device` property with device/OS/browser detection capabilities.
|
|
15
|
+
* The device service is initialized in connectedCallback and follows component lifecycle.
|
|
16
|
+
*
|
|
17
|
+
* @typeParam T - The Lit component class being extended
|
|
18
|
+
* @returns Extended class with device detection capabilities
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { LitElement } from 'lit';
|
|
23
|
+
* import { customElement } from 'lit/decorators.js';
|
|
24
|
+
* import { withDeviceService } from '@lukso/core/mixins';
|
|
25
|
+
*
|
|
26
|
+
* @customElement('my-component')
|
|
27
|
+
* export class MyComponent extends withDeviceService(LitElement) {
|
|
28
|
+
* render() {
|
|
29
|
+
* return html\`Device is mobile: \${this.device?.isMobile}\`;
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
export function withDeviceService<T extends typeof LitElement>(Base: T): any {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
class Mixin extends (Base as any) {
|
|
38
|
+
device: DeviceService | undefined
|
|
39
|
+
|
|
40
|
+
connectedCallback(): void {
|
|
41
|
+
super.connectedCallback()
|
|
42
|
+
this.device = deviceService(navigator as NavigatorExtended)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
return Mixin as any
|
|
48
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internationalization Mixin
|
|
3
|
+
*
|
|
4
|
+
* Mixin to add internationalization service to a Lit component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { effect } from '@preact/signals-core'
|
|
8
|
+
import type { LitElement } from 'lit'
|
|
9
|
+
import englishTranslations from '../../../translations/en_US.json'
|
|
10
|
+
import {
|
|
11
|
+
createIntlService,
|
|
12
|
+
defaultConfig,
|
|
13
|
+
getIntlService,
|
|
14
|
+
type IntlService,
|
|
15
|
+
setIntlService,
|
|
16
|
+
} from '../services/intl.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Mixin to add internationalization service to a Lit component
|
|
20
|
+
*
|
|
21
|
+
* Provides access to the global intl service with reactive locale changes.
|
|
22
|
+
* Automatically subscribes to locale changes and triggers re-renders.
|
|
23
|
+
*
|
|
24
|
+
* The component will use the global intl service if available, or create a local one.
|
|
25
|
+
* This follows the singleton pattern for the global service while allowing flexibility.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam T - The Lit component class being extended
|
|
28
|
+
* @returns Extended class with intl service capabilities
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* import { LitElement, html } from 'lit';
|
|
33
|
+
* import { customElement } from 'lit/decorators.js';
|
|
34
|
+
* import { withIntlService } from '@lukso/core/mixins';
|
|
35
|
+
*
|
|
36
|
+
* @customElement('my-component')
|
|
37
|
+
* export class MyComponent extends withIntlService(LitElement) {
|
|
38
|
+
* render() {
|
|
39
|
+
* return html\`<p>\${this.formatMessage('app.welcome')}</p>\`;
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
export function withIntlService<T extends typeof LitElement>(Base: T): any {
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
class Mixin extends (Base as any) {
|
|
48
|
+
protected unsubscribeIntl: (() => void) | undefined = undefined
|
|
49
|
+
|
|
50
|
+
connectedCallback(): void {
|
|
51
|
+
super.connectedCallback()
|
|
52
|
+
|
|
53
|
+
// Subscribe to intl changes via signal
|
|
54
|
+
let intl: IntlService | null = getIntlService()
|
|
55
|
+
|
|
56
|
+
// When no intl is provided by host app we initialize our own
|
|
57
|
+
if (!intl) {
|
|
58
|
+
intl = this.setupLocalIntl() ?? null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (intl) {
|
|
62
|
+
this.unsubscribeIntl = effect(() => {
|
|
63
|
+
// Access the signal to track changes
|
|
64
|
+
intl?.localeChanged.value
|
|
65
|
+
this.requestUpdate()
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Set fallback for missing translations
|
|
70
|
+
intl?.setFallbackTranslations(englishTranslations as any)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
disconnectedCallback(): void {
|
|
74
|
+
super.disconnectedCallback()
|
|
75
|
+
|
|
76
|
+
// Unsubscribe from intl changes
|
|
77
|
+
if (typeof this.unsubscribeIntl === 'function') {
|
|
78
|
+
this.unsubscribeIntl()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Setup a local intl service with default configuration
|
|
84
|
+
* Subclasses can override this to customize initialization
|
|
85
|
+
*/
|
|
86
|
+
protected setupLocalIntl(): IntlService | undefined {
|
|
87
|
+
const intlService = createIntlService(
|
|
88
|
+
Object.assign(defaultConfig, {
|
|
89
|
+
messages: englishTranslations,
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
setIntlService(intlService)
|
|
93
|
+
return intlService
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Format message using the intl service
|
|
98
|
+
*/
|
|
99
|
+
formatMessage(key?: string, options?: Record<string, string>): string {
|
|
100
|
+
if (!key) {
|
|
101
|
+
console.warn('No translation key provided to formatMessage')
|
|
102
|
+
return ''
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const intl = getIntlService()
|
|
106
|
+
return intl?.formatMessage(key, options) ?? key
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
return Mixin as any
|
|
112
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { deviceService } from '../device'
|
|
3
|
+
|
|
4
|
+
describe('deviceService', () => {
|
|
5
|
+
it('should create a device service instance', () => {
|
|
6
|
+
const device = deviceService()
|
|
7
|
+
expect(device).toBeDefined()
|
|
8
|
+
expect(device.getRaw).toBeDefined()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should have device detection properties', () => {
|
|
12
|
+
const device = deviceService()
|
|
13
|
+
expect(device).toHaveProperty('isMobile')
|
|
14
|
+
expect(device).toHaveProperty('isTablet')
|
|
15
|
+
expect(device).toHaveProperty('isDesktop')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('should have OS detection properties', () => {
|
|
19
|
+
const device = deviceService()
|
|
20
|
+
expect(device).toHaveProperty('isIOS')
|
|
21
|
+
expect(device).toHaveProperty('isAndroid')
|
|
22
|
+
expect(device).toHaveProperty('isWindows')
|
|
23
|
+
expect(device).toHaveProperty('isMacOS')
|
|
24
|
+
expect(device).toHaveProperty('isLinux')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should have browser detection properties', () => {
|
|
28
|
+
const device = deviceService()
|
|
29
|
+
expect(device).toHaveProperty('isChrome')
|
|
30
|
+
expect(device).toHaveProperty('isSafari')
|
|
31
|
+
expect(device).toHaveProperty('isFirefox')
|
|
32
|
+
expect(device).toHaveProperty('isEdge')
|
|
33
|
+
expect(device).toHaveProperty('isOpera')
|
|
34
|
+
expect(device).toHaveProperty('isBrave')
|
|
35
|
+
})
|
|
36
|
+
})
|