@nyaruka/temba-components 0.131.2 → 0.131.3
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 +14 -0
- package/demo/components/floating-tabs/example.html +400 -0
- package/demo/components/flow/index.html +1 -1
- package/demo/data/flows/sample-flow.json +41 -2
- package/demo/data/flows/voicemail.json +613 -0
- package/demo/index.html +6 -0
- package/dist/locales/es.js +5 -5
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fr.js +5 -5
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/locale-codes.js +11 -2
- package/dist/locales/locale-codes.js.map +1 -1
- package/dist/locales/pt.js +5 -5
- package/dist/locales/pt.js.map +1 -1
- package/dist/temba-components.js +1109 -535
- package/dist/temba-components.js.map +1 -1
- package/out-tsc/src/display/FloatingTab.js +167 -0
- package/out-tsc/src/display/FloatingTab.js.map +1 -0
- package/out-tsc/src/display/ProgressBar.js +22 -2
- package/out-tsc/src/display/ProgressBar.js.map +1 -1
- package/out-tsc/src/events.js.map +1 -1
- package/out-tsc/src/flow/CanvasNode.js +165 -31
- package/out-tsc/src/flow/CanvasNode.js.map +1 -1
- package/out-tsc/src/flow/Editor.js +857 -3
- package/out-tsc/src/flow/Editor.js.map +1 -1
- package/out-tsc/src/flow/NodeEditor.js +239 -19
- package/out-tsc/src/flow/NodeEditor.js.map +1 -1
- package/out-tsc/src/flow/NodeTypeSelector.js +44 -3
- package/out-tsc/src/flow/NodeTypeSelector.js.map +1 -1
- package/out-tsc/src/flow/StickyNote.js +12 -3
- package/out-tsc/src/flow/StickyNote.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js +2 -1
- package/out-tsc/src/flow/actions/add_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js +2 -1
- package/out-tsc/src/flow/actions/add_contact_urn.js.map +1 -1
- package/out-tsc/src/flow/actions/add_input_labels.js +2 -1
- package/out-tsc/src/flow/actions/add_input_labels.js.map +1 -1
- package/out-tsc/src/flow/actions/play_audio.js +2 -1
- package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js +2 -1
- package/out-tsc/src/flow/actions/remove_contact_groups.js.map +1 -1
- package/out-tsc/src/flow/actions/request_optin.js +1 -0
- package/out-tsc/src/flow/actions/request_optin.js.map +1 -1
- package/out-tsc/src/flow/actions/say_msg.js +2 -1
- package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/send_broadcast.js +2 -1
- package/out-tsc/src/flow/actions/send_broadcast.js.map +1 -1
- package/out-tsc/src/flow/actions/send_email.js +2 -1
- package/out-tsc/src/flow/actions/send_email.js.map +1 -1
- package/out-tsc/src/flow/actions/send_msg.js +93 -3
- package/out-tsc/src/flow/actions/send_msg.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_channel.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_field.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_field.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_language.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_language.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_name.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_name.js.map +1 -1
- package/out-tsc/src/flow/actions/set_contact_status.js +2 -1
- package/out-tsc/src/flow/actions/set_contact_status.js.map +1 -1
- package/out-tsc/src/flow/actions/set_run_result.js +2 -1
- package/out-tsc/src/flow/actions/set_run_result.js.map +1 -1
- package/out-tsc/src/flow/actions/start_session.js +2 -1
- package/out-tsc/src/flow/actions/start_session.js.map +1 -1
- package/out-tsc/src/flow/config.js +2 -10
- package/out-tsc/src/flow/config.js.map +1 -1
- package/out-tsc/src/flow/nodes/shared.js +54 -0
- package/out-tsc/src/flow/nodes/shared.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_airtime.js +9 -3
- package/out-tsc/src/flow/nodes/split_by_airtime.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_contact_field.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_contact_field.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_expression.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_expression.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_groups.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_groups.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_intent.js +3 -2
- package/out-tsc/src/flow/nodes/split_by_intent.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm.js +9 -2
- package/out-tsc/src/flow/nodes/split_by_llm.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js +9 -2
- package/out-tsc/src/flow/nodes/split_by_llm_categorize.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_random.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_random.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_resthook.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_resthook.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_run_result.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_run_result.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_scheme.js +8 -3
- package/out-tsc/src/flow/nodes/split_by_scheme.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_subflow.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_subflow.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_ticket.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_ticket.js.map +1 -1
- package/out-tsc/src/flow/nodes/split_by_webhook.js +8 -2
- package/out-tsc/src/flow/nodes/split_by_webhook.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_digits.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_menu.js +3 -2
- package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
- package/out-tsc/src/flow/nodes/wait_for_response.js +8 -3
- package/out-tsc/src/flow/nodes/wait_for_response.js.map +1 -1
- package/out-tsc/src/flow/types.js +15 -0
- package/out-tsc/src/flow/types.js.map +1 -1
- package/out-tsc/src/layout/FloatingWindow.js +346 -0
- package/out-tsc/src/layout/FloatingWindow.js.map +1 -0
- package/out-tsc/src/live/ContactChat.js +3 -19
- package/out-tsc/src/live/ContactChat.js.map +1 -1
- package/out-tsc/src/locales/es.js +5 -5
- package/out-tsc/src/locales/es.js.map +1 -1
- package/out-tsc/src/locales/fr.js +5 -5
- package/out-tsc/src/locales/fr.js.map +1 -1
- package/out-tsc/src/locales/locale-codes.js +11 -2
- package/out-tsc/src/locales/locale-codes.js.map +1 -1
- package/out-tsc/src/locales/pt.js +5 -5
- package/out-tsc/src/locales/pt.js.map +1 -1
- package/out-tsc/src/store/AppState.js +67 -0
- package/out-tsc/src/store/AppState.js.map +1 -1
- package/out-tsc/temba-modules.js +4 -0
- package/out-tsc/temba-modules.js.map +1 -1
- package/out-tsc/test/temba-floating-tab.test.js +91 -0
- package/out-tsc/test/temba-floating-tab.test.js.map +1 -0
- package/out-tsc/test/temba-floating-window.test.js +301 -0
- package/out-tsc/test/temba-floating-window.test.js.map +1 -0
- package/out-tsc/test/temba-flow-editor-node.test.js +117 -0
- package/out-tsc/test/temba-flow-editor-node.test.js.map +1 -1
- package/out-tsc/test/temba-localization.test.js +471 -0
- package/out-tsc/test/temba-localization.test.js.map +1 -0
- package/out-tsc/test/temba-node-type-selector.test.js +150 -0
- package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
- package/out-tsc/test/utils.test.js +18 -0
- package/out-tsc/test/utils.test.js.map +1 -1
- package/package.json +1 -1
- package/screenshots/truth/floating-tab/default.png +0 -0
- package/screenshots/truth/floating-tab/gray.png +0 -0
- package/screenshots/truth/floating-tab/green.png +0 -0
- package/screenshots/truth/floating-tab/hidden.png +0 -0
- package/screenshots/truth/floating-tab/hover.png +0 -0
- package/screenshots/truth/floating-tab/purple.png +0 -0
- package/screenshots/truth/floating-window/chromeless.png +0 -0
- package/screenshots/truth/floating-window/custom-size.png +0 -0
- package/screenshots/truth/floating-window/default.png +0 -0
- package/screenshots/truth/floating-window/with-header.png +0 -0
- package/screenshots/truth/node-type-selector/action-mode.png +0 -0
- package/screenshots/truth/node-type-selector/split-mode.png +0 -0
- package/screenshots/truth/nodes/split_by_llm_categorize/editor/feedback-categorization.png +0 -0
- package/src/display/FloatingTab.ts +174 -0
- package/src/display/ProgressBar.ts +22 -2
- package/src/events.ts +2 -4
- package/src/flow/CanvasNode.ts +190 -32
- package/src/flow/Editor.ts +1040 -3
- package/src/flow/NodeEditor.ts +317 -19
- package/src/flow/NodeTypeSelector.ts +47 -3
- package/src/flow/StickyNote.ts +12 -3
- package/src/flow/actions/add_contact_groups.ts +2 -1
- package/src/flow/actions/add_contact_urn.ts +3 -1
- package/src/flow/actions/add_input_labels.ts +2 -1
- package/src/flow/actions/play_audio.ts +2 -1
- package/src/flow/actions/remove_contact_groups.ts +3 -1
- package/src/flow/actions/request_optin.ts +1 -0
- package/src/flow/actions/say_msg.ts +2 -1
- package/src/flow/actions/send_broadcast.ts +2 -1
- package/src/flow/actions/send_email.ts +3 -1
- package/src/flow/actions/send_msg.ts +134 -3
- package/src/flow/actions/set_contact_channel.ts +2 -1
- package/src/flow/actions/set_contact_field.ts +2 -1
- package/src/flow/actions/set_contact_language.ts +3 -1
- package/src/flow/actions/set_contact_name.ts +2 -1
- package/src/flow/actions/set_contact_status.ts +2 -1
- package/src/flow/actions/set_run_result.ts +2 -1
- package/src/flow/actions/start_session.ts +3 -1
- package/src/flow/config.ts +2 -12
- package/src/flow/nodes/shared.ts +70 -1
- package/src/flow/nodes/split_by_airtime.ts +20 -3
- package/src/flow/nodes/split_by_contact_field.ts +13 -3
- package/src/flow/nodes/split_by_expression.ts +13 -3
- package/src/flow/nodes/split_by_groups.ts +13 -3
- package/src/flow/nodes/split_by_intent.ts +3 -2
- package/src/flow/nodes/split_by_llm.ts +19 -2
- package/src/flow/nodes/split_by_llm_categorize.ts +19 -2
- package/src/flow/nodes/split_by_random.ts +12 -2
- package/src/flow/nodes/split_by_resthook.ts +13 -3
- package/src/flow/nodes/split_by_run_result.ts +13 -3
- package/src/flow/nodes/split_by_scheme.ts +13 -3
- package/src/flow/nodes/split_by_subflow.ts +12 -2
- package/src/flow/nodes/split_by_ticket.ts +12 -2
- package/src/flow/nodes/split_by_webhook.ts +12 -2
- package/src/flow/nodes/wait_for_digits.ts +3 -2
- package/src/flow/nodes/wait_for_menu.ts +3 -2
- package/src/flow/nodes/wait_for_response.ts +13 -3
- package/src/flow/types.ts +47 -0
- package/src/layout/FloatingWindow.ts +386 -0
- package/src/live/ContactChat.ts +4 -19
- package/src/locales/es.ts +18 -13
- package/src/locales/fr.ts +18 -13
- package/src/locales/locale-codes.ts +11 -2
- package/src/locales/pt.ts +18 -13
- package/src/store/AppState.ts +104 -0
- package/static/api/llms.json +18 -0
- package/temba-modules.ts +4 -0
- package/test/temba-floating-tab.test.ts +110 -0
- package/test/temba-floating-window.test.ts +477 -0
- package/test/temba-flow-editor-node.test.ts +144 -0
- package/test/temba-localization.test.ts +611 -0
- package/test/temba-node-type-selector.test.ts +203 -0
- package/test/utils.test.ts +20 -0
- package/test-assets/contacts/history.json +5 -6
- package/test-assets/select/llms.json +2 -2
- package/web-dev-server.config.mjs +47 -1
- package/web-test-runner.config.mjs +0 -1
- package/out-tsc/src/flow/nodes/wait_for_audio.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_audio.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_image.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_image.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_location.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_location.js.map +0 -1
- package/out-tsc/src/flow/nodes/wait_for_video.js +0 -7
- package/out-tsc/src/flow/nodes/wait_for_video.js.map +0 -1
- package/src/flow/nodes/wait_for_audio.ts +0 -7
- package/src/flow/nodes/wait_for_image.ts +0 -7
- package/src/flow/nodes/wait_for_location.ts +0 -7
- package/src/flow/nodes/wait_for_video.ts +0 -7
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { expect, assert } from '@open-wc/testing';
|
|
2
|
+
import { FloatingTab } from '../src/display/FloatingTab';
|
|
3
|
+
import { assertScreenshot, getClip, getComponent } from './utils.test';
|
|
4
|
+
|
|
5
|
+
describe('temba-floating-tab', () => {
|
|
6
|
+
it('can be created', async () => {
|
|
7
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
8
|
+
icon: 'phone',
|
|
9
|
+
label: 'Phone Simulator',
|
|
10
|
+
color: '#10b981',
|
|
11
|
+
top: 100
|
|
12
|
+
})) as FloatingTab;
|
|
13
|
+
|
|
14
|
+
assert.instanceOf(tab, FloatingTab);
|
|
15
|
+
expect(tab.icon).to.equal('phone');
|
|
16
|
+
expect(tab.label).to.equal('Phone Simulator');
|
|
17
|
+
expect(tab.color).to.equal('#10b981');
|
|
18
|
+
expect(tab.top).to.equal(100);
|
|
19
|
+
expect(tab.hidden).to.equal(false);
|
|
20
|
+
|
|
21
|
+
await assertScreenshot('floating-tab/default', getClip(tab));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('can be hidden', async () => {
|
|
25
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
26
|
+
icon: 'phone',
|
|
27
|
+
label: 'Phone Simulator',
|
|
28
|
+
color: '#10b981',
|
|
29
|
+
hidden: true
|
|
30
|
+
})) as FloatingTab;
|
|
31
|
+
|
|
32
|
+
expect(tab.hidden).to.equal(true);
|
|
33
|
+
expect(tab.classList.contains('hidden')).to.equal(true);
|
|
34
|
+
|
|
35
|
+
await assertScreenshot('floating-tab/hidden', getClip(tab));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('shows label on hover', async () => {
|
|
39
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
40
|
+
icon: 'phone',
|
|
41
|
+
label: 'Phone Simulator',
|
|
42
|
+
color: '#6366f1'
|
|
43
|
+
})) as FloatingTab;
|
|
44
|
+
|
|
45
|
+
const tabElement = tab.shadowRoot.querySelector('.tab') as HTMLElement;
|
|
46
|
+
expect(tabElement).to.exist;
|
|
47
|
+
|
|
48
|
+
// simulate hover state
|
|
49
|
+
const labelElement = tab.shadowRoot.querySelector('.label') as HTMLElement;
|
|
50
|
+
expect(labelElement).to.exist;
|
|
51
|
+
|
|
52
|
+
await assertScreenshot('floating-tab/hover', getClip(tab));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('fires click event', async () => {
|
|
56
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
57
|
+
icon: 'clock',
|
|
58
|
+
label: 'History',
|
|
59
|
+
color: '#8b5cf6'
|
|
60
|
+
})) as FloatingTab;
|
|
61
|
+
|
|
62
|
+
let clicked = false;
|
|
63
|
+
tab.addEventListener('temba-button-clicked', () => {
|
|
64
|
+
clicked = true;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const tabElement = tab.shadowRoot.querySelector('.tab') as HTMLElement;
|
|
68
|
+
tabElement.click();
|
|
69
|
+
|
|
70
|
+
expect(clicked).to.equal(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('supports different colors', async () => {
|
|
74
|
+
const tab1 = (await getComponent('temba-floating-tab', {
|
|
75
|
+
icon: 'phone',
|
|
76
|
+
label: 'Phone',
|
|
77
|
+
color: '#10b981',
|
|
78
|
+
top: 100
|
|
79
|
+
})) as FloatingTab;
|
|
80
|
+
|
|
81
|
+
const tab2 = (await getComponent('temba-floating-tab', {
|
|
82
|
+
icon: 'globe',
|
|
83
|
+
label: 'Translation',
|
|
84
|
+
color: '#6b7280',
|
|
85
|
+
top: 200
|
|
86
|
+
})) as FloatingTab;
|
|
87
|
+
|
|
88
|
+
const tab3 = (await getComponent('temba-floating-tab', {
|
|
89
|
+
icon: 'clock',
|
|
90
|
+
label: 'History',
|
|
91
|
+
color: '#8b5cf6',
|
|
92
|
+
top: 300
|
|
93
|
+
})) as FloatingTab;
|
|
94
|
+
|
|
95
|
+
await assertScreenshot('floating-tab/green', getClip(tab1));
|
|
96
|
+
await assertScreenshot('floating-tab/gray', getClip(tab2));
|
|
97
|
+
await assertScreenshot('floating-tab/purple', getClip(tab3));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('supports custom positioning', async () => {
|
|
101
|
+
const tab = (await getComponent('temba-floating-tab', {
|
|
102
|
+
icon: 'phone',
|
|
103
|
+
label: 'Phone Simulator',
|
|
104
|
+
color: '#10b981',
|
|
105
|
+
top: 250
|
|
106
|
+
})) as FloatingTab;
|
|
107
|
+
|
|
108
|
+
expect(tab.top).to.equal(250);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { expect, assert } from '@open-wc/testing';
|
|
2
|
+
import { FloatingWindow } from '../src/layout/FloatingWindow';
|
|
3
|
+
import { assertScreenshot, getComponent } from './utils.test';
|
|
4
|
+
|
|
5
|
+
describe('temba-floating-window', () => {
|
|
6
|
+
it('can be created', async () => {
|
|
7
|
+
const window = (await getComponent(
|
|
8
|
+
'temba-floating-window',
|
|
9
|
+
{
|
|
10
|
+
header: 'Phone Simulator',
|
|
11
|
+
width: 250,
|
|
12
|
+
maxHeight: 700,
|
|
13
|
+
top: 100
|
|
14
|
+
},
|
|
15
|
+
'<div style="padding: 20px;">Window content goes here</div>',
|
|
16
|
+
300,
|
|
17
|
+
750
|
|
18
|
+
)) as FloatingWindow;
|
|
19
|
+
|
|
20
|
+
assert.instanceOf(window, FloatingWindow);
|
|
21
|
+
expect(window.header).to.equal('Phone Simulator');
|
|
22
|
+
expect(window.width).to.equal(250);
|
|
23
|
+
expect(window.maxHeight).to.equal(700);
|
|
24
|
+
expect(window.top).to.equal(100);
|
|
25
|
+
|
|
26
|
+
// show the window for screenshot
|
|
27
|
+
window.hidden = false;
|
|
28
|
+
await window.updateComplete;
|
|
29
|
+
expect(window.hidden).to.equal(false);
|
|
30
|
+
|
|
31
|
+
// use custom clip for fixed positioned element
|
|
32
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
33
|
+
'.window'
|
|
34
|
+
) as HTMLElement;
|
|
35
|
+
const clip = {
|
|
36
|
+
x: window.left,
|
|
37
|
+
y: window.top,
|
|
38
|
+
width: window.width,
|
|
39
|
+
height: windowElement.offsetHeight
|
|
40
|
+
};
|
|
41
|
+
await assertScreenshot('floating-window/default', clip);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('starts hidden by default', async () => {
|
|
45
|
+
const window = (await getComponent(
|
|
46
|
+
'temba-floating-window',
|
|
47
|
+
{
|
|
48
|
+
header: 'Test Window'
|
|
49
|
+
},
|
|
50
|
+
'<div>Content</div>'
|
|
51
|
+
)) as FloatingWindow;
|
|
52
|
+
|
|
53
|
+
expect(window.hidden).to.equal(true);
|
|
54
|
+
expect(window.classList.contains('hidden')).to.equal(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('can be shown and hidden', async () => {
|
|
58
|
+
const window = (await getComponent(
|
|
59
|
+
'temba-floating-window',
|
|
60
|
+
{
|
|
61
|
+
header: 'Test Window',
|
|
62
|
+
hidden: true
|
|
63
|
+
},
|
|
64
|
+
'<div>Content</div>'
|
|
65
|
+
)) as FloatingWindow;
|
|
66
|
+
|
|
67
|
+
expect(window.hidden).to.equal(true);
|
|
68
|
+
|
|
69
|
+
window.show();
|
|
70
|
+
await window.updateComplete;
|
|
71
|
+
expect(window.hidden).to.equal(false);
|
|
72
|
+
expect(window.classList.contains('hidden')).to.equal(false);
|
|
73
|
+
|
|
74
|
+
window.hide();
|
|
75
|
+
await window.updateComplete;
|
|
76
|
+
expect(window.hidden).to.equal(true);
|
|
77
|
+
expect(window.classList.contains('hidden')).to.equal(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('fires close event when close button clicked', async () => {
|
|
81
|
+
const window = (await getComponent(
|
|
82
|
+
'temba-floating-window',
|
|
83
|
+
{
|
|
84
|
+
header: 'Test Window'
|
|
85
|
+
},
|
|
86
|
+
'<div>Content</div>',
|
|
87
|
+
300,
|
|
88
|
+
750
|
|
89
|
+
)) as FloatingWindow;
|
|
90
|
+
|
|
91
|
+
// show the window first
|
|
92
|
+
window.hidden = false;
|
|
93
|
+
await window.updateComplete;
|
|
94
|
+
|
|
95
|
+
let closed = false;
|
|
96
|
+
window.addEventListener('temba-dialog-hidden', () => {
|
|
97
|
+
closed = true;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const closeButton = window.shadowRoot.querySelector(
|
|
101
|
+
'.close-button'
|
|
102
|
+
) as HTMLElement;
|
|
103
|
+
expect(closeButton).to.exist;
|
|
104
|
+
|
|
105
|
+
closeButton.click();
|
|
106
|
+
await window.updateComplete;
|
|
107
|
+
|
|
108
|
+
expect(closed).to.equal(true);
|
|
109
|
+
expect(window.hidden).to.equal(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('displays header correctly', async () => {
|
|
113
|
+
const window = (await getComponent(
|
|
114
|
+
'temba-floating-window',
|
|
115
|
+
{
|
|
116
|
+
header: 'Phone Simulator'
|
|
117
|
+
},
|
|
118
|
+
'<div>Content</div>',
|
|
119
|
+
300,
|
|
120
|
+
400
|
|
121
|
+
)) as FloatingWindow;
|
|
122
|
+
|
|
123
|
+
window.hidden = false;
|
|
124
|
+
await window.updateComplete;
|
|
125
|
+
|
|
126
|
+
const titleElement = window.shadowRoot.querySelector('.title');
|
|
127
|
+
expect(titleElement).to.exist;
|
|
128
|
+
expect(titleElement.textContent).to.equal('Phone Simulator');
|
|
129
|
+
|
|
130
|
+
// use custom clip for fixed positioned element
|
|
131
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
132
|
+
'.window'
|
|
133
|
+
) as HTMLElement;
|
|
134
|
+
const clip = {
|
|
135
|
+
x: window.left,
|
|
136
|
+
y: window.top,
|
|
137
|
+
width: window.width,
|
|
138
|
+
height: windowElement.offsetHeight
|
|
139
|
+
};
|
|
140
|
+
await assertScreenshot('floating-window/with-header', clip);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('renders slot content', async () => {
|
|
144
|
+
const window = (await getComponent(
|
|
145
|
+
'temba-floating-window',
|
|
146
|
+
{
|
|
147
|
+
header: 'Test'
|
|
148
|
+
},
|
|
149
|
+
'<div class="test-content">Custom content</div>',
|
|
150
|
+
300,
|
|
151
|
+
400
|
|
152
|
+
)) as FloatingWindow;
|
|
153
|
+
|
|
154
|
+
window.hidden = false;
|
|
155
|
+
await window.updateComplete;
|
|
156
|
+
|
|
157
|
+
const slotContent = window.querySelector('.test-content');
|
|
158
|
+
expect(slotContent).to.exist;
|
|
159
|
+
expect(slotContent.textContent).to.equal('Custom content');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('supports custom dimensions', async () => {
|
|
163
|
+
const window = (await getComponent(
|
|
164
|
+
'temba-floating-window',
|
|
165
|
+
{
|
|
166
|
+
header: 'Custom Size',
|
|
167
|
+
width: 400,
|
|
168
|
+
maxHeight: 600,
|
|
169
|
+
top: 100,
|
|
170
|
+
left: 100
|
|
171
|
+
},
|
|
172
|
+
'<div>Content</div>',
|
|
173
|
+
450,
|
|
174
|
+
650
|
|
175
|
+
)) as FloatingWindow;
|
|
176
|
+
|
|
177
|
+
window.show();
|
|
178
|
+
await window.updateComplete;
|
|
179
|
+
expect(window.width).to.equal(400);
|
|
180
|
+
expect(window.maxHeight).to.equal(600);
|
|
181
|
+
expect(window.top).to.equal(100);
|
|
182
|
+
expect(window.left).to.equal(100);
|
|
183
|
+
|
|
184
|
+
// use custom clip for fixed positioned element
|
|
185
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
186
|
+
'.window'
|
|
187
|
+
) as HTMLElement;
|
|
188
|
+
const clip = {
|
|
189
|
+
x: window.left,
|
|
190
|
+
y: window.top,
|
|
191
|
+
width: window.width,
|
|
192
|
+
height: windowElement.offsetHeight
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await assertScreenshot('floating-window/custom-size', clip);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('can be dragged by header', async () => {
|
|
199
|
+
const window = (await getComponent(
|
|
200
|
+
'temba-floating-window',
|
|
201
|
+
{
|
|
202
|
+
header: 'Draggable Window',
|
|
203
|
+
width: 250,
|
|
204
|
+
maxHeight: 400,
|
|
205
|
+
top: 100,
|
|
206
|
+
left: 100
|
|
207
|
+
},
|
|
208
|
+
'<div>Content</div>',
|
|
209
|
+
300,
|
|
210
|
+
450
|
|
211
|
+
)) as FloatingWindow;
|
|
212
|
+
|
|
213
|
+
window.hidden = false;
|
|
214
|
+
await window.updateComplete;
|
|
215
|
+
|
|
216
|
+
const header = window.shadowRoot.querySelector('.header') as HTMLElement;
|
|
217
|
+
expect(header).to.exist;
|
|
218
|
+
|
|
219
|
+
// simulate drag by setting dragging state
|
|
220
|
+
window.dragging = true;
|
|
221
|
+
await window.updateComplete;
|
|
222
|
+
|
|
223
|
+
const windowElement = window.shadowRoot.querySelector('.window');
|
|
224
|
+
expect(windowElement.classList.contains('dragging')).to.equal(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('respects viewport bounds when dragging', async () => {
|
|
228
|
+
const window = (await getComponent(
|
|
229
|
+
'temba-floating-window',
|
|
230
|
+
{
|
|
231
|
+
header: 'Bounded Window',
|
|
232
|
+
width: 250,
|
|
233
|
+
maxHeight: 400,
|
|
234
|
+
top: 100,
|
|
235
|
+
left: 100
|
|
236
|
+
},
|
|
237
|
+
'<div style="height: 200px;">Content with specific height</div>',
|
|
238
|
+
300,
|
|
239
|
+
450
|
|
240
|
+
)) as FloatingWindow;
|
|
241
|
+
|
|
242
|
+
window.hidden = false;
|
|
243
|
+
await window.updateComplete;
|
|
244
|
+
|
|
245
|
+
// get actual window height
|
|
246
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
247
|
+
'.window'
|
|
248
|
+
) as HTMLElement;
|
|
249
|
+
const actualHeight = windowElement.offsetHeight;
|
|
250
|
+
|
|
251
|
+
// simulate dragging near bottom of viewport
|
|
252
|
+
const viewportHeight = window.ownerDocument.defaultView.innerHeight;
|
|
253
|
+
const maxAllowedTop = viewportHeight - actualHeight;
|
|
254
|
+
|
|
255
|
+
// try to drag below the viewport
|
|
256
|
+
window.top = viewportHeight + 100;
|
|
257
|
+
await window.updateComplete;
|
|
258
|
+
|
|
259
|
+
// the handleMouseMove should clamp this, but we'll test the logic exists
|
|
260
|
+
expect(actualHeight).to.be.greaterThan(0);
|
|
261
|
+
expect(maxAllowedTop).to.be.lessThan(viewportHeight);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('maintains consistent starting position', async () => {
|
|
265
|
+
const window = (await getComponent(
|
|
266
|
+
'temba-floating-window',
|
|
267
|
+
{
|
|
268
|
+
header: 'Test',
|
|
269
|
+
width: 250,
|
|
270
|
+
maxHeight: 400,
|
|
271
|
+
top: 100,
|
|
272
|
+
left: 100
|
|
273
|
+
},
|
|
274
|
+
'<div>Content</div>',
|
|
275
|
+
300,
|
|
276
|
+
450
|
|
277
|
+
)) as FloatingWindow;
|
|
278
|
+
|
|
279
|
+
window.hidden = false;
|
|
280
|
+
await window.updateComplete;
|
|
281
|
+
|
|
282
|
+
// verify initial position matches properties
|
|
283
|
+
expect(window.top).to.equal(100);
|
|
284
|
+
expect(window.left).to.equal(100);
|
|
285
|
+
|
|
286
|
+
// change position (simulating drag)
|
|
287
|
+
window.top = 200;
|
|
288
|
+
window.left = 200;
|
|
289
|
+
await window.updateComplete;
|
|
290
|
+
|
|
291
|
+
// hide and show
|
|
292
|
+
window.hide();
|
|
293
|
+
await window.updateComplete;
|
|
294
|
+
window.show();
|
|
295
|
+
await window.updateComplete;
|
|
296
|
+
|
|
297
|
+
// position should remain at property values (100, 100) not dragged position
|
|
298
|
+
expect(window.top).to.equal(100);
|
|
299
|
+
expect(window.left).to.equal(100);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('can disable chrome', async () => {
|
|
303
|
+
const window = (await getComponent(
|
|
304
|
+
'temba-floating-window',
|
|
305
|
+
{
|
|
306
|
+
header: 'Test',
|
|
307
|
+
width: 250,
|
|
308
|
+
maxHeight: 400,
|
|
309
|
+
top: 100,
|
|
310
|
+
left: 100,
|
|
311
|
+
chromeless: true
|
|
312
|
+
},
|
|
313
|
+
'<div style="background: white; padding: 20px;">Chromeless content</div>',
|
|
314
|
+
300,
|
|
315
|
+
450
|
|
316
|
+
)) as FloatingWindow;
|
|
317
|
+
|
|
318
|
+
expect(window.chromeless).to.equal(true);
|
|
319
|
+
|
|
320
|
+
window.hidden = false;
|
|
321
|
+
await window.updateComplete;
|
|
322
|
+
|
|
323
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
324
|
+
'.window'
|
|
325
|
+
) as HTMLElement;
|
|
326
|
+
expect(windowElement.classList.contains('chromeless')).to.equal(true);
|
|
327
|
+
|
|
328
|
+
// header should not be rendered
|
|
329
|
+
const header = window.shadowRoot.querySelector('.header');
|
|
330
|
+
expect(header).to.not.exist;
|
|
331
|
+
|
|
332
|
+
// body should have no padding
|
|
333
|
+
const body = window.shadowRoot.querySelector('.body') as HTMLElement;
|
|
334
|
+
const bodyStyles = getComputedStyle(body);
|
|
335
|
+
expect(bodyStyles.padding).to.equal('0px');
|
|
336
|
+
|
|
337
|
+
// use custom clip for fixed positioned element
|
|
338
|
+
const clip = {
|
|
339
|
+
x: window.left,
|
|
340
|
+
y: window.top,
|
|
341
|
+
width: window.width,
|
|
342
|
+
height: windowElement.offsetHeight
|
|
343
|
+
};
|
|
344
|
+
await assertScreenshot('floating-window/chromeless', clip);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('defaults to showing chrome', async () => {
|
|
348
|
+
const window = (await getComponent(
|
|
349
|
+
'temba-floating-window',
|
|
350
|
+
{
|
|
351
|
+
header: 'Test'
|
|
352
|
+
},
|
|
353
|
+
'<div>Content</div>'
|
|
354
|
+
)) as FloatingWindow;
|
|
355
|
+
|
|
356
|
+
expect(window.chromeless).to.equal(false);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('can close via public close() method', async () => {
|
|
360
|
+
const window = (await getComponent(
|
|
361
|
+
'temba-floating-window',
|
|
362
|
+
{
|
|
363
|
+
header: 'Test',
|
|
364
|
+
chromeless: true
|
|
365
|
+
},
|
|
366
|
+
'<div>Content</div>'
|
|
367
|
+
)) as FloatingWindow;
|
|
368
|
+
|
|
369
|
+
window.hidden = false;
|
|
370
|
+
await window.updateComplete;
|
|
371
|
+
expect(window.hidden).to.equal(false);
|
|
372
|
+
|
|
373
|
+
let eventFired = false;
|
|
374
|
+
window.addEventListener('temba-dialog-hidden', () => {
|
|
375
|
+
eventFired = true;
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// call public close() method
|
|
379
|
+
window.close();
|
|
380
|
+
await window.updateComplete;
|
|
381
|
+
|
|
382
|
+
expect(window.hidden).to.equal(true);
|
|
383
|
+
expect(eventFired).to.equal(true);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('chromeless window has no borders or shadows', async () => {
|
|
387
|
+
const window = (await getComponent(
|
|
388
|
+
'temba-floating-window',
|
|
389
|
+
{
|
|
390
|
+
header: 'Test',
|
|
391
|
+
width: 250,
|
|
392
|
+
maxHeight: 400,
|
|
393
|
+
chromeless: true
|
|
394
|
+
},
|
|
395
|
+
'<div>Content</div>',
|
|
396
|
+
300,
|
|
397
|
+
450
|
|
398
|
+
)) as FloatingWindow;
|
|
399
|
+
|
|
400
|
+
window.hidden = false;
|
|
401
|
+
await window.updateComplete;
|
|
402
|
+
|
|
403
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
404
|
+
'.window'
|
|
405
|
+
) as HTMLElement;
|
|
406
|
+
const styles = getComputedStyle(windowElement);
|
|
407
|
+
|
|
408
|
+
expect(styles.boxShadow).to.equal('none');
|
|
409
|
+
expect(styles.borderRadius).to.equal('0px');
|
|
410
|
+
expect(styles.background.includes('rgba(0, 0, 0, 0)')).to.be.true;
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
it('supports min and max height constraints', async () => {
|
|
414
|
+
const window = (await getComponent(
|
|
415
|
+
'temba-floating-window',
|
|
416
|
+
{
|
|
417
|
+
header: 'Min/Max Test',
|
|
418
|
+
width: 300,
|
|
419
|
+
minHeight: 200,
|
|
420
|
+
maxHeight: 500
|
|
421
|
+
},
|
|
422
|
+
'<div style="padding: 20px;">Content that can vary in height</div>',
|
|
423
|
+
350,
|
|
424
|
+
550
|
|
425
|
+
)) as FloatingWindow;
|
|
426
|
+
|
|
427
|
+
window.hidden = false;
|
|
428
|
+
await window.updateComplete;
|
|
429
|
+
|
|
430
|
+
expect(window.minHeight).to.equal(200);
|
|
431
|
+
expect(window.maxHeight).to.equal(500);
|
|
432
|
+
|
|
433
|
+
// verify the styles are applied
|
|
434
|
+
const windowElement = window.shadowRoot.querySelector(
|
|
435
|
+
'.window'
|
|
436
|
+
) as HTMLElement;
|
|
437
|
+
const styles = getComputedStyle(windowElement);
|
|
438
|
+
expect(styles.minHeight).to.equal('200px');
|
|
439
|
+
expect(styles.maxHeight).to.equal('500px');
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('stays on screen when browser is resized', async () => {
|
|
443
|
+
const window = (await getComponent(
|
|
444
|
+
'temba-floating-window',
|
|
445
|
+
{
|
|
446
|
+
header: 'Resize Test',
|
|
447
|
+
width: 250,
|
|
448
|
+
maxHeight: 400,
|
|
449
|
+
top: 100,
|
|
450
|
+
left: 100
|
|
451
|
+
},
|
|
452
|
+
'<div style="height: 200px;">Content</div>',
|
|
453
|
+
300,
|
|
454
|
+
450
|
|
455
|
+
)) as FloatingWindow;
|
|
456
|
+
|
|
457
|
+
window.hidden = false;
|
|
458
|
+
await window.updateComplete;
|
|
459
|
+
|
|
460
|
+
// position window near right edge
|
|
461
|
+
const originalViewportWidth = window.ownerDocument.defaultView.innerWidth;
|
|
462
|
+
window.left = originalViewportWidth - window.width - 30;
|
|
463
|
+
await window.updateComplete;
|
|
464
|
+
|
|
465
|
+
// simulate window resize event (the component should constrain position)
|
|
466
|
+
window.dispatchEvent(new Event('resize', { bubbles: true }));
|
|
467
|
+
await window.updateComplete;
|
|
468
|
+
|
|
469
|
+
// window should still be within viewport bounds with 20px padding
|
|
470
|
+
const padding = 20;
|
|
471
|
+
expect(window.left).to.be.at.least(padding);
|
|
472
|
+
expect(window.left).to.be.at.most(
|
|
473
|
+
window.ownerDocument.defaultView.innerWidth - window.width - padding
|
|
474
|
+
);
|
|
475
|
+
expect(window.top).to.be.at.least(padding);
|
|
476
|
+
});
|
|
477
|
+
});
|