@lowdefy/blocks-basic 0.0.0-experimental-20260914131506 → 0.0.0-experimental-20260925210809
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/dist/blocks/Anchor/meta.js +1 -1
- package/dist/blocks/ClickableHtml/ClickableHtml.js +32 -0
- package/dist/blocks/ClickableHtml/e2e.js +23 -0
- package/dist/blocks/ClickableHtml/gallery.yaml +180 -0
- package/dist/blocks/ClickableHtml/meta.js +53 -0
- package/dist/blocks/ClickableHtml/tests/ClickableHtml.e2e.yaml +69 -0
- package/dist/blocks/ClickableHtml/tests.yaml +35 -0
- package/dist/blocks/DangerousHtml/DangerousHtml.js +10 -20
- package/dist/blocks/DangerousHtml/meta.js +1 -1
- package/dist/blocks/Html/gallery.yaml +28 -0
- package/dist/blocks/Html/meta.js +16 -1
- package/dist/blocks/Icon/Icon.js +21 -1
- package/dist/blocks/Icon/gallery.yaml +59 -0
- package/dist/blocks/Icon/meta.js +2 -2
- package/dist/blocks/PollingTimer/PollingTimer.js +120 -0
- package/dist/blocks/PollingTimer/e2e.js +21 -0
- package/dist/blocks/PollingTimer/gallery.yaml +190 -0
- package/dist/blocks/PollingTimer/meta.js +61 -0
- package/dist/blocks/PollingTimer/validateInterval.js +21 -0
- package/dist/blocks.js +2 -0
- package/dist/e2e.js +2 -0
- package/dist/metas.js +2 -0
- package/package.json +9 -6
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
'string',
|
|
66
66
|
'object'
|
|
67
67
|
],
|
|
68
|
-
description: "
|
|
68
|
+
description: "Icon name (a semantic name like edit, or a <a href='https://react-icons.github.io/react-icons/'>React Icons</a> name like LuPencil) or properties of an Icon block for anchor icon.",
|
|
69
69
|
docs: {
|
|
70
70
|
displayType: 'icon'
|
|
71
71
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import React from 'react';
|
|
16
|
+
import { withBlockDefaults, HtmlComponent } from '@lowdefy/block-utils';
|
|
17
|
+
// Each clickable element names the event it fires in its data-event attribute
|
|
18
|
+
// (data-event="onEditClick" → events.onEditClick), so every target in the
|
|
19
|
+
// markup has its own action chain. Its other data-* attributes are the event
|
|
20
|
+
// object. HtmlComponent finds the target, including inside data-popover
|
|
21
|
+
// content, and prevents the element's default action.
|
|
22
|
+
const ClickableHtml = ({ blockId, classNames, events, properties, methods, styles })=>/*#__PURE__*/ React.createElement(HtmlComponent, {
|
|
23
|
+
div: true,
|
|
24
|
+
events: events,
|
|
25
|
+
html: properties.html,
|
|
26
|
+
id: blockId,
|
|
27
|
+
methods: methods,
|
|
28
|
+
className: classNames?.element,
|
|
29
|
+
style: styles?.element,
|
|
30
|
+
onDataEvent: methods.triggerEvent
|
|
31
|
+
});
|
|
32
|
+
export default withBlockDefaults(ClickableHtml);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { createBlockHelper, escapeId } from '@lowdefy/e2e-utils';
|
|
16
|
+
import { expect } from '@playwright/test';
|
|
17
|
+
const locator = (page, blockId)=>page.locator(`#${escapeId(blockId)}`);
|
|
18
|
+
export default createBlockHelper({
|
|
19
|
+
locator,
|
|
20
|
+
expect: {
|
|
21
|
+
containsText: (page, blockId, text)=>expect(locator(page, blockId)).toContainText(text)
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
- title: A named event per button
|
|
15
|
+
blocks:
|
|
16
|
+
- id: buttons_basic
|
|
17
|
+
type: ClickableHtml
|
|
18
|
+
properties:
|
|
19
|
+
html: '<p>Pick a size: <button data-event="onSmallClick">Small</button> <button data-event="onLargeClick">Large</button></p>'
|
|
20
|
+
events:
|
|
21
|
+
onSmallClick:
|
|
22
|
+
- id: set_small
|
|
23
|
+
type: SetState
|
|
24
|
+
params:
|
|
25
|
+
buttons_basic_selected: small
|
|
26
|
+
onLargeClick:
|
|
27
|
+
- id: set_large
|
|
28
|
+
type: SetState
|
|
29
|
+
params:
|
|
30
|
+
buttons_basic_selected: large
|
|
31
|
+
- id: buttons_basic_result
|
|
32
|
+
type: Html
|
|
33
|
+
properties:
|
|
34
|
+
html:
|
|
35
|
+
_string.concat:
|
|
36
|
+
- '<p>State: <code>'
|
|
37
|
+
- _state:
|
|
38
|
+
key: buttons_basic_selected
|
|
39
|
+
default: 'nothing yet'
|
|
40
|
+
- '</code></p>'
|
|
41
|
+
|
|
42
|
+
- title: Data attributes are the event object
|
|
43
|
+
blocks:
|
|
44
|
+
- id: list_rows
|
|
45
|
+
type: ClickableHtml
|
|
46
|
+
properties:
|
|
47
|
+
html: '<ul style="list-style: none; padding: 0; margin: 0;"><li data-event="onRowClick" data-record-id="rec_1" style="padding: 8px; cursor: pointer; border-bottom: 1px solid var(--ant-color-border);">First record</li><li data-event="onRowClick" data-record-id="rec_2" style="padding: 8px; cursor: pointer; border-bottom: 1px solid var(--ant-color-border);">Second record</li><li data-event="onRowClick" data-record-id="rec_3" style="padding: 8px; cursor: pointer;">Third record</li></ul>'
|
|
48
|
+
events:
|
|
49
|
+
onRowClick:
|
|
50
|
+
- id: set_clicked
|
|
51
|
+
type: SetState
|
|
52
|
+
params:
|
|
53
|
+
list_rows_clicked:
|
|
54
|
+
_event: true
|
|
55
|
+
- id: list_rows_result
|
|
56
|
+
type: Html
|
|
57
|
+
properties:
|
|
58
|
+
html:
|
|
59
|
+
_string.concat:
|
|
60
|
+
- '<p>Last event: <code>'
|
|
61
|
+
- _json.stringify:
|
|
62
|
+
_state:
|
|
63
|
+
key: list_rows_clicked
|
|
64
|
+
default: {}
|
|
65
|
+
- '</code></p>'
|
|
66
|
+
|
|
67
|
+
- title: Links and plain markup are left to the browser
|
|
68
|
+
blocks:
|
|
69
|
+
- id: mixed_content
|
|
70
|
+
type: ClickableHtml
|
|
71
|
+
properties:
|
|
72
|
+
html: '<p>This <a href="https://lowdefy.com" target="_blank">link</a> navigates normally, but <a href="#" data-event="onCommentClick" data-thread-id="t_9">this one</a> fires onCommentClick instead.</p>'
|
|
73
|
+
events:
|
|
74
|
+
onCommentClick:
|
|
75
|
+
- id: set_mixed
|
|
76
|
+
type: SetState
|
|
77
|
+
params:
|
|
78
|
+
mixed_content_thread:
|
|
79
|
+
_event: thread_id
|
|
80
|
+
- id: mixed_content_result
|
|
81
|
+
type: Html
|
|
82
|
+
properties:
|
|
83
|
+
html:
|
|
84
|
+
_string.concat:
|
|
85
|
+
- '<p>Thread: <code>'
|
|
86
|
+
- _state:
|
|
87
|
+
key: mixed_content_thread
|
|
88
|
+
default: 'none'
|
|
89
|
+
- '</code></p>'
|
|
90
|
+
|
|
91
|
+
- title: Style Overrides
|
|
92
|
+
blocks:
|
|
93
|
+
- id: style_card
|
|
94
|
+
type: ClickableHtml
|
|
95
|
+
style:
|
|
96
|
+
.element:
|
|
97
|
+
padding: 16
|
|
98
|
+
borderRadius: 8
|
|
99
|
+
border: 1px solid var(--ant-color-border)
|
|
100
|
+
background: var(--ant-color-bg-container)
|
|
101
|
+
properties:
|
|
102
|
+
html: '<h4 style="margin: 0 0 8px 0;">Card</h4><p style="margin: 0 0 8px 0; color: var(--ant-color-text-secondary);">Yours to change or remove until tomorrow.</p><button data-event="onEditClick">Edit</button> <button data-event="onRemoveClick">Remove</button>'
|
|
103
|
+
events:
|
|
104
|
+
onEditClick:
|
|
105
|
+
- id: set_card_edit
|
|
106
|
+
type: SetState
|
|
107
|
+
params:
|
|
108
|
+
style_card_action: edit
|
|
109
|
+
onRemoveClick:
|
|
110
|
+
- id: set_card_remove
|
|
111
|
+
type: SetState
|
|
112
|
+
params:
|
|
113
|
+
style_card_action: remove
|
|
114
|
+
|
|
115
|
+
- title: Clickable icons and a popover menu
|
|
116
|
+
blocks:
|
|
117
|
+
- id: row_actions
|
|
118
|
+
type: ClickableHtml
|
|
119
|
+
properties:
|
|
120
|
+
html: '<p>Invoice INV-042 <i data-icon="edit" data-event="onEdit" data-id="42" data-tooltip="Edit" style="cursor: pointer"></i> <span data-popover="more" style="cursor: pointer"><i data-icon="more-vertical"></i></span></p><div data-popover-content="more" hidden><p data-event="onDuplicate" data-id="42" style="cursor: pointer; margin: 0 0 8px"><i data-icon="copy"></i> Duplicate</p><p data-event="onDelete" data-id="42" style="cursor: pointer; margin: 0; color: var(--ant-color-error)"><i data-icon="delete"></i> Delete</p></div>'
|
|
121
|
+
events:
|
|
122
|
+
onEdit:
|
|
123
|
+
- id: set_edit
|
|
124
|
+
type: SetState
|
|
125
|
+
params:
|
|
126
|
+
row_actions_last: { _string.concat: ['edit ', { _event: id }] }
|
|
127
|
+
onDuplicate:
|
|
128
|
+
- id: set_duplicate
|
|
129
|
+
type: SetState
|
|
130
|
+
params:
|
|
131
|
+
row_actions_last: { _string.concat: ['duplicate ', { _event: id }] }
|
|
132
|
+
onDelete:
|
|
133
|
+
- id: set_delete
|
|
134
|
+
type: SetState
|
|
135
|
+
params:
|
|
136
|
+
row_actions_last: { _string.concat: ['delete ', { _event: id }] }
|
|
137
|
+
- id: row_actions_result
|
|
138
|
+
type: Html
|
|
139
|
+
properties:
|
|
140
|
+
html:
|
|
141
|
+
_string.concat:
|
|
142
|
+
- '<p>Last action: <code>'
|
|
143
|
+
- _state:
|
|
144
|
+
key: row_actions_last
|
|
145
|
+
default: 'none yet'
|
|
146
|
+
- '</code></p>'
|
|
147
|
+
|
|
148
|
+
- title: Confirm before an event
|
|
149
|
+
blocks:
|
|
150
|
+
- id: confirm_actions
|
|
151
|
+
type: ClickableHtml
|
|
152
|
+
properties:
|
|
153
|
+
html: '<p>INV-2026-0042 <i data-icon="delete" data-event="onDelete" data-id="42" data-tooltip="Delete" data-confirm="Delete invoice INV-2026-0042?" style="cursor: pointer; color: var(--ant-color-error)"></i> <span data-event="onArchive" data-id="42" data-confirm style="cursor: pointer; color: var(--ant-color-primary)">Archive</span></p>'
|
|
154
|
+
events:
|
|
155
|
+
onDelete:
|
|
156
|
+
- id: deleted
|
|
157
|
+
type: SetState
|
|
158
|
+
params:
|
|
159
|
+
confirm_last:
|
|
160
|
+
_string.concat:
|
|
161
|
+
- 'deleted '
|
|
162
|
+
- _event: id
|
|
163
|
+
onArchive:
|
|
164
|
+
- id: archived
|
|
165
|
+
type: SetState
|
|
166
|
+
params:
|
|
167
|
+
confirm_last:
|
|
168
|
+
_string.concat:
|
|
169
|
+
- 'archived '
|
|
170
|
+
- _event: id
|
|
171
|
+
- id: confirm_result
|
|
172
|
+
type: Html
|
|
173
|
+
properties:
|
|
174
|
+
html:
|
|
175
|
+
_string.concat:
|
|
176
|
+
- '<p>Last action: <code>'
|
|
177
|
+
- _state:
|
|
178
|
+
key: confirm_last
|
|
179
|
+
default: 'none yet'
|
|
180
|
+
- '</code></p>'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ export default {
|
|
16
|
+
category: 'display',
|
|
17
|
+
icons: [],
|
|
18
|
+
valueType: null,
|
|
19
|
+
cssKeys: {
|
|
20
|
+
element: 'The Html element.'
|
|
21
|
+
},
|
|
22
|
+
hazards: [
|
|
23
|
+
{
|
|
24
|
+
id: 'html-style-stripped',
|
|
25
|
+
message: 'Content is sanitised with DOMPurify, so <style>, <script> and inline event handlers are removed before render. Clicks are wired through data-event attributes instead of inline handlers.',
|
|
26
|
+
see: 'display-blocks/clickablehtml'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'clickable-html-confirm-destructive',
|
|
30
|
+
message: 'Add data-confirm="Delete this row?" to a data-event element whose event deletes or changes data, so a stray click cannot fire it. Only OK fires the event.',
|
|
31
|
+
see: 'concepts/html-attributes'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
// Each clickable element names its event in a data-event attribute, so the
|
|
35
|
+
// event names this block fires are authored in its html.
|
|
36
|
+
dynamicEvents: true,
|
|
37
|
+
events: {
|
|
38
|
+
onTextSelection: 'Trigger action when text is selected and pass selected text to the event object.'
|
|
39
|
+
},
|
|
40
|
+
properties: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
properties: {
|
|
44
|
+
html: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Content to be rendered as Html. An element with a data-event attribute fires the event it names when clicked (data-event="onEditClick" fires events.onEditClick), and its default browser action is prevented. The event object holds the element\'s other data-* attributes with snake_case keys, so data-event="onEditClick" data-record-id="42" gives { record_id: "42" }. Targets that are not links or buttons become keyboard focusable, and Enter or Space clicks them. A data-event inside popover content fires too, then closes the popover. A link with data-event fires the event and does not navigate. data-confirm="Delete this row?" on a data-event element asks first: only OK fires the event (bare data-confirm asks "Are you sure?"). All Html block attributes work too: data-icon, data-tooltip, data-popover, data-page-id links, data-new-tab, data-tag, data-status, data-time, data-format, data-avatar, data-copy, data-truncate and data-tone. See the HTML attributes docs page.',
|
|
47
|
+
docs: {
|
|
48
|
+
displayType: 'text-area'
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
id: clickablehtml
|
|
15
|
+
type: Box
|
|
16
|
+
|
|
17
|
+
blocks:
|
|
18
|
+
- id: clickable_basic
|
|
19
|
+
type: ClickableHtml
|
|
20
|
+
properties:
|
|
21
|
+
html: '<p>Hello <button data-event="onGreetClick" data-user-id="u_1">World</button> <button data-event="onWaveClick" data-user-id="u_2">Wave</button></p>'
|
|
22
|
+
events:
|
|
23
|
+
onGreetClick:
|
|
24
|
+
- id: set_greeted
|
|
25
|
+
type: SetState
|
|
26
|
+
params:
|
|
27
|
+
clicked:
|
|
28
|
+
event: greet
|
|
29
|
+
user_id:
|
|
30
|
+
_event: user_id
|
|
31
|
+
onWaveClick:
|
|
32
|
+
- id: set_waved
|
|
33
|
+
type: SetState
|
|
34
|
+
params:
|
|
35
|
+
clicked:
|
|
36
|
+
event: wave
|
|
37
|
+
user_id:
|
|
38
|
+
_event: user_id
|
|
39
|
+
|
|
40
|
+
- id: clickable_result
|
|
41
|
+
type: Html
|
|
42
|
+
properties:
|
|
43
|
+
html:
|
|
44
|
+
_string.concat:
|
|
45
|
+
- '<span id="clicked_event">'
|
|
46
|
+
- _state:
|
|
47
|
+
key: clicked.event
|
|
48
|
+
default: ''
|
|
49
|
+
- '</span><span id="clicked_user_id">'
|
|
50
|
+
- _state:
|
|
51
|
+
key: clicked.user_id
|
|
52
|
+
default: ''
|
|
53
|
+
- '</span>'
|
|
54
|
+
|
|
55
|
+
- id: clickable_no_event
|
|
56
|
+
type: ClickableHtml
|
|
57
|
+
properties:
|
|
58
|
+
html: '<p><button>Plain button</button></p>'
|
|
59
|
+
events:
|
|
60
|
+
onPlainClick:
|
|
61
|
+
- id: set_plain
|
|
62
|
+
type: SetState
|
|
63
|
+
params:
|
|
64
|
+
plain_clicked: true
|
|
65
|
+
|
|
66
|
+
- id: clickable_sanitized
|
|
67
|
+
type: ClickableHtml
|
|
68
|
+
properties:
|
|
69
|
+
html: <p>Safe content</p><script>alert('xss')</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
- id: default
|
|
15
|
+
type: ClickableHtml
|
|
16
|
+
- id: properties.html
|
|
17
|
+
type: ClickableHtml
|
|
18
|
+
properties:
|
|
19
|
+
html: |
|
|
20
|
+
<p>Plain markup <button data-event="onGoClick" data-item-id="1">Go</button></p>
|
|
21
|
+
- id: properties.html-no-events
|
|
22
|
+
type: ClickableHtml
|
|
23
|
+
properties:
|
|
24
|
+
html: |
|
|
25
|
+
<p>No data-event here, clicks do nothing.</p>
|
|
26
|
+
- id: properties.html-bad-code
|
|
27
|
+
type: ClickableHtml
|
|
28
|
+
properties:
|
|
29
|
+
html: |
|
|
30
|
+
<button data-event="onOkClick" onclick="alert('inline handler stripped')">Ok</button>
|
|
31
|
+
<script>alert('script tag');</script>
|
|
32
|
+
- id: properties.html is number 0
|
|
33
|
+
type: ClickableHtml
|
|
34
|
+
properties:
|
|
35
|
+
html: 0
|
|
@@ -13,44 +13,34 @@
|
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React from 'react';
|
|
16
|
-
import
|
|
17
|
-
import { withBlockDefaults } from '@lowdefy/block-utils';
|
|
18
|
-
import { type } from '@lowdefy/helpers';
|
|
16
|
+
import { HtmlComponent, withBlockDefaults } from '@lowdefy/block-utils';
|
|
19
17
|
let DangerousHtml = class DangerousHtml extends React.Component {
|
|
20
|
-
|
|
21
|
-
const {
|
|
18
|
+
warnOnOptionsChange() {
|
|
19
|
+
const { DOMPurifyOptions } = this.props.properties;
|
|
22
20
|
if (!this.warnedOptionsChange && JSON.stringify(DOMPurifyOptions) !== JSON.stringify(this.DOMPurifyOptions)) {
|
|
23
21
|
this.warnedOptionsChange = true;
|
|
24
22
|
console.warn(`DangerousHtml block "${this.props.blockId}": DOMPurifyOptions changed after mount and the change is ignored. The sanitizer options are fixed at the first render; write them as a literal, not from state.`);
|
|
25
23
|
}
|
|
26
|
-
const htmlString = type.isNone(html) ? '' : html.toString();
|
|
27
|
-
this.div.innerHTML = DOMPurify.sanitize(htmlString, this.DOMPurifyOptions);
|
|
28
24
|
}
|
|
29
25
|
componentDidMount() {
|
|
30
|
-
this.
|
|
26
|
+
this.warnOnOptionsChange();
|
|
31
27
|
}
|
|
32
28
|
componentDidUpdate() {
|
|
33
|
-
this.
|
|
29
|
+
this.warnOnOptionsChange();
|
|
34
30
|
}
|
|
35
31
|
render() {
|
|
36
|
-
const { blockId, classNames, styles } = this.props;
|
|
37
|
-
return /*#__PURE__*/ React.createElement(
|
|
32
|
+
const { blockId, classNames, properties, styles } = this.props;
|
|
33
|
+
return /*#__PURE__*/ React.createElement(HtmlComponent, {
|
|
34
|
+
div: true,
|
|
35
|
+
html: properties.html,
|
|
38
36
|
id: blockId,
|
|
39
|
-
"data-testid": blockId,
|
|
40
|
-
ref: (el)=>{
|
|
41
|
-
if (el) {
|
|
42
|
-
this.div = el;
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
37
|
className: classNames?.element,
|
|
38
|
+
sanitizeOptions: this.DOMPurifyOptions,
|
|
46
39
|
style: styles?.element
|
|
47
40
|
});
|
|
48
41
|
}
|
|
49
42
|
constructor(props){
|
|
50
43
|
super(props);
|
|
51
|
-
this.div = {
|
|
52
|
-
innerHTML: ''
|
|
53
|
-
};
|
|
54
44
|
// The sanitizer options are fixed at mount on purpose: a later, operator-driven
|
|
55
45
|
// value could loosen the sanitizer from state. A change is refused loudly
|
|
56
46
|
// rather than ignored silently.
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
properties: {
|
|
38
38
|
html: {
|
|
39
39
|
type: 'string',
|
|
40
|
-
description: 'Content to be rendered as Html.',
|
|
40
|
+
description: 'Content to be rendered as Html. The Html block attributes (data-icon, data-tooltip, data-popover, data-page-id links, data-new-tab, data-tag, data-status, data-time, data-format, data-avatar, data-copy, data-truncate, data-tone) work here too, unless DOMPurifyOptions removes data attributes.',
|
|
41
41
|
docs: {
|
|
42
42
|
displayType: 'text-area'
|
|
43
43
|
}
|
|
@@ -237,3 +237,31 @@
|
|
|
237
237
|
border: 1px solid var(--ant-color-border)
|
|
238
238
|
properties:
|
|
239
239
|
html: '<h4 style="margin: 0 0 8px 0;">Elevated Block</h4><p style="margin: 0;">Uses bg-elevated token for a surface that adapts to light and dark mode.</p>'
|
|
240
|
+
|
|
241
|
+
- title: Icons, tooltips and popovers
|
|
242
|
+
blocks:
|
|
243
|
+
- id: html_icons
|
|
244
|
+
type: Html
|
|
245
|
+
properties:
|
|
246
|
+
html: '<p><i data-icon="success" style="color: var(--ant-color-success)"></i> Saved <span data-tooltip="Saved to the cloud 2 minutes ago" style="text-decoration: underline dotted">just now</span></p><p><i data-icon="warning" style="color: var(--ant-color-warning)"></i> 3 fields need attention. <span data-popover="fields" style="color: var(--ant-color-primary); cursor: pointer">Which? <i data-icon="chevron-down"></i></span></p><div data-popover-content="fields" hidden><ul style="margin: 0; padding-left: 16px"><li>Email</li><li>Phone</li><li>Address</li></ul></div>'
|
|
247
|
+
|
|
248
|
+
- title: Links and status tags
|
|
249
|
+
blocks:
|
|
250
|
+
- id: html_links_tags
|
|
251
|
+
type: Html
|
|
252
|
+
properties:
|
|
253
|
+
html: '<p><a data-page-id="Button">Button</a> moved to <span data-tag="success">Released</span> · <a data-page-id="theming" data-url-query="section=icons">Theming</a> is <span data-tag="warning">In review</span> · <a href="https://ant.design" data-new-tab>Ant Design</a> <i data-icon="external-link"></i></p><p><span data-status="success">Online</span> <span data-status="processing">Syncing</span> <span data-status="error">Offline</span></p><p><span data-tag>Design</span> <span data-tag>Engineering</span> <span data-tag>Support</span> <span data-tag="purple">Beta</span> <span data-tag="#0f766e">Custom colour</span></p>'
|
|
254
|
+
|
|
255
|
+
- title: Dates, numbers, avatars and copy
|
|
256
|
+
blocks:
|
|
257
|
+
- id: html_time_format
|
|
258
|
+
type: Html
|
|
259
|
+
properties:
|
|
260
|
+
html: '<p><span data-avatar="Jane Doe"></span> Jane Doe updated the invoice <time datetime="2026-01-05T09:30:00Z" data-time="relative"></time> (<time datetime="2026-01-05T09:30:00Z" data-time="D MMM YYYY"></time>) · <span data-format="currency" data-currency="USD">1234.5</span> · <span data-format="percent" data-decimals="1">0.123</span> of target · <span data-format="bytes">1536000</span> attached</p><p>Invoice number <code data-copy>INV-2026-0042</code> · owner <img data-avatar="Sam Smith" src="" style="--lf-avatar-size: 32px"></p>'
|
|
261
|
+
|
|
262
|
+
- title: Truncation and text tone
|
|
263
|
+
blocks:
|
|
264
|
+
- id: html_truncate_tone
|
|
265
|
+
type: Html
|
|
266
|
+
properties:
|
|
267
|
+
html: '<div style="max-width: 320px"><div data-truncate><b>Quarterly report for the northern region, including every branch and its targets</b></div><div data-truncate="2" data-tone="secondary">The report covers revenue, costs and headcount for each branch, compares them with the same quarter last year, and lists the actions each branch manager agreed for the next quarter.</div><div><span data-tone="tertiary">Updated 3 days ago</span> · <span data-tone="success">On track</span> · <span data-tone="quaternary">—</span></div></div>'
|
package/dist/blocks/Html/meta.js
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
id: 'html-style-stripped',
|
|
25
25
|
message: 'Content is sanitised with DOMPurify, so <style>, <script> and inline event handlers are removed before render. Use DangerousHtml for a trusted <style> block, or style the block through its style and class properties.',
|
|
26
26
|
see: 'display-blocks/html'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'html-no-click-events',
|
|
30
|
+
message: 'Html fires no events from its markup: data-event attributes do nothing here. Use ClickableHtml for clickable elements, including icons.',
|
|
31
|
+
see: 'display-blocks/clickablehtml'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'html-hardcoded-links',
|
|
35
|
+
message: 'Link to app pages with <a data-page-id="page-id" data-url-query="key=value">, not <a href="/page-id?key=value">. A hard-coded href reloads the whole app on click, ignores basePath and is not checked by the build. Use data-new-tab, not target="_blank", which sanitising removes.',
|
|
36
|
+
see: 'concepts/html-attributes'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'html-inline-status-pills',
|
|
40
|
+
message: 'Show statuses with <span data-tag="success">Approved</span> or <span data-status="warning">Syncing</span>, not inline-styled pills or hex status colours. data-tag and data-status follow the theme and dark mode and match the ag-grid tag cells.',
|
|
41
|
+
see: 'concepts/html-attributes'
|
|
27
42
|
}
|
|
28
43
|
],
|
|
29
44
|
events: {
|
|
@@ -35,7 +50,7 @@
|
|
|
35
50
|
properties: {
|
|
36
51
|
html: {
|
|
37
52
|
type: 'string',
|
|
38
|
-
description: 'Content to be rendered as Html.',
|
|
53
|
+
description: 'Content to be rendered as Html. It understands these attributes: data-icon="edit" renders an icon (a semantic name like edit, delete, warning, or a React Icons name); data-tooltip="Text" shows a tooltip on hover and focus; data-popover="id" toggles a popover showing the element with data-popover-content="id" (mark it hidden); <a data-page-id="page-id" data-url-query="key=value"> links to an app page without reloading (never hard-code href="/page"); data-link opts an existing app-relative <a href="/path"> into the same navigation; data-new-tab opens a link in a new tab (target is stripped by sanitising); data-tag="success" renders a tinted status tag and data-status="success" a status dot (tones success, processing, info, warning, error, default, an antd preset colour, a CSS colour, or any other value to seed a stable colour); <time datetime="ISO" data-time="relative"> renders a date as relative, date, datetime, time or a dayjs format; data-format="currency" data-currency="USD" formats the element\'s number as number, currency, percent, compact or bytes; data-avatar="Jane Doe" renders initials (on an <img>, a fallback for a broken image; never use an avatar image service); data-copy adds a copy button for the element\'s text or the attribute\'s value; data-truncate="2" clamps block text to 1–6 lines with the full text in a tooltip when cut off; data-tone="secondary" colours text with a theme tone (secondary, tertiary, quaternary, success, warning, error, info). See the HTML attributes docs page.',
|
|
39
54
|
docs: {
|
|
40
55
|
displayType: 'text-area'
|
|
41
56
|
}
|
package/dist/blocks/Icon/Icon.js
CHANGED
|
@@ -14,5 +14,25 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ import React from 'react';
|
|
16
16
|
import { withBlockDefaults } from '@lowdefy/block-utils';
|
|
17
|
-
|
|
17
|
+
// An Icon block with an onClick event is a control, so it takes keyboard focus
|
|
18
|
+
// and Enter/Space fire the event like a click. The icon component other blocks
|
|
19
|
+
// render (inside a Button, an Alert) stays untouched: their block is the control.
|
|
20
|
+
const IconBlock = ({ components: { Icon }, events, methods, ...props })=>{
|
|
21
|
+
const keyboardProps = events.onClick ? {
|
|
22
|
+
tabIndex: 0,
|
|
23
|
+
onKeyDown: (event)=>{
|
|
24
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
25
|
+
event.preventDefault();
|
|
26
|
+
methods.triggerEvent({
|
|
27
|
+
name: 'onClick'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
} : {};
|
|
31
|
+
return /*#__PURE__*/ React.createElement(Icon, {
|
|
32
|
+
events: events,
|
|
33
|
+
methods: methods,
|
|
34
|
+
...keyboardProps,
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
};
|
|
18
38
|
export default withBlockDefaults(IconBlock);
|
|
@@ -12,6 +12,65 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
- title: Semantic icon names
|
|
16
|
+
blocks:
|
|
17
|
+
- id: semantic_add
|
|
18
|
+
type: Icon
|
|
19
|
+
layout:
|
|
20
|
+
flex: 0 0 auto
|
|
21
|
+
properties:
|
|
22
|
+
name: add
|
|
23
|
+
size: 24
|
|
24
|
+
- id: semantic_edit
|
|
25
|
+
type: Icon
|
|
26
|
+
layout:
|
|
27
|
+
flex: 0 0 auto
|
|
28
|
+
properties:
|
|
29
|
+
name: edit
|
|
30
|
+
size: 24
|
|
31
|
+
- id: semantic_delete
|
|
32
|
+
type: Icon
|
|
33
|
+
layout:
|
|
34
|
+
flex: 0 0 auto
|
|
35
|
+
properties:
|
|
36
|
+
name: delete
|
|
37
|
+
size: 24
|
|
38
|
+
- id: semantic_search
|
|
39
|
+
type: Icon
|
|
40
|
+
layout:
|
|
41
|
+
flex: 0 0 auto
|
|
42
|
+
properties:
|
|
43
|
+
name: search
|
|
44
|
+
size: 24
|
|
45
|
+
- id: semantic_settings
|
|
46
|
+
type: Icon
|
|
47
|
+
layout:
|
|
48
|
+
flex: 0 0 auto
|
|
49
|
+
properties:
|
|
50
|
+
name: settings
|
|
51
|
+
size: 24
|
|
52
|
+
- id: semantic_warning
|
|
53
|
+
type: Icon
|
|
54
|
+
layout:
|
|
55
|
+
flex: 0 0 auto
|
|
56
|
+
properties:
|
|
57
|
+
name: warning
|
|
58
|
+
size: 24
|
|
59
|
+
- id: semantic_info
|
|
60
|
+
type: Icon
|
|
61
|
+
layout:
|
|
62
|
+
flex: 0 0 auto
|
|
63
|
+
properties:
|
|
64
|
+
name: info
|
|
65
|
+
size: 24
|
|
66
|
+
- id: semantic_success
|
|
67
|
+
type: Icon
|
|
68
|
+
layout:
|
|
69
|
+
flex: 0 0 auto
|
|
70
|
+
properties:
|
|
71
|
+
name: success
|
|
72
|
+
size: 24
|
|
73
|
+
|
|
15
74
|
- title: Common Icons
|
|
16
75
|
blocks:
|
|
17
76
|
- id: icon_home
|
package/dist/blocks/Icon/meta.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
element: 'The Icon element.'
|
|
23
23
|
},
|
|
24
24
|
events: {
|
|
25
|
-
onClick: 'Trigger actions when icon is clicked.'
|
|
25
|
+
onClick: 'Trigger actions when icon is clicked. The icon then takes keyboard focus, and Enter or Space trigger it too.'
|
|
26
26
|
},
|
|
27
27
|
properties: {
|
|
28
28
|
type: 'object',
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
name: {
|
|
39
39
|
type: 'string',
|
|
40
40
|
default: 'AiOutlineCloseCircle',
|
|
41
|
-
description: 'Name of icon
|
|
41
|
+
description: 'Name of the icon: a semantic name like edit, delete or warning (add your own with theme.icons.aliases), or a React Icons name like LuPencil.'
|
|
42
42
|
},
|
|
43
43
|
rotate: {
|
|
44
44
|
type: 'number',
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { useEffect, useRef } from 'react';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
import { withBlockDefaults } from '@lowdefy/block-utils';
|
|
18
|
+
import validateInterval from './validateInterval.js';
|
|
19
|
+
// Headless timer: renders nothing and fires onTick while running. Typically used to poll a
|
|
20
|
+
// request until a background job completes. Each tick is scheduled "interval" ms after the
|
|
21
|
+
// previous onTick actions finish, so a slow chain never overlaps the next tick.
|
|
22
|
+
function PollingTimer({ methods, properties }) {
|
|
23
|
+
// Validated while rendering so an invalid interval reaches the block error boundary, instead
|
|
24
|
+
// of throwing from a timeout or visibility listener where nothing surfaces it.
|
|
25
|
+
validateInterval({
|
|
26
|
+
interval: properties.interval
|
|
27
|
+
});
|
|
28
|
+
const timeoutRef = useRef(null);
|
|
29
|
+
const scheduledIntervalRef = useRef(null);
|
|
30
|
+
const inFlightRef = useRef(false);
|
|
31
|
+
const runningRef = useRef(false);
|
|
32
|
+
const tickRef = useRef(0);
|
|
33
|
+
// Methods are registered once, so property changes are read through a ref.
|
|
34
|
+
const propertiesRef = useRef(properties);
|
|
35
|
+
propertiesRef.current = properties;
|
|
36
|
+
function hiddenAndPaused() {
|
|
37
|
+
if (propertiesRef.current.pauseWhenHidden === false) return false;
|
|
38
|
+
return document.visibilityState === 'hidden';
|
|
39
|
+
}
|
|
40
|
+
function clearScheduledTick() {
|
|
41
|
+
if (timeoutRef.current === null) return;
|
|
42
|
+
clearTimeout(timeoutRef.current);
|
|
43
|
+
timeoutRef.current = null;
|
|
44
|
+
}
|
|
45
|
+
function scheduleTick() {
|
|
46
|
+
if (!runningRef.current) return;
|
|
47
|
+
if (timeoutRef.current !== null) return;
|
|
48
|
+
// A tick in flight schedules the next one when its onTick actions finish.
|
|
49
|
+
if (inFlightRef.current) return;
|
|
50
|
+
if (hiddenAndPaused()) return;
|
|
51
|
+
const { interval } = propertiesRef.current;
|
|
52
|
+
scheduledIntervalRef.current = interval;
|
|
53
|
+
timeoutRef.current = setTimeout(tick, interval);
|
|
54
|
+
}
|
|
55
|
+
async function tick() {
|
|
56
|
+
timeoutRef.current = null;
|
|
57
|
+
tickRef.current += 1;
|
|
58
|
+
const { maxTicks } = propertiesRef.current;
|
|
59
|
+
if (type.isInt(maxTicks) && tickRef.current >= maxTicks) {
|
|
60
|
+
runningRef.current = false;
|
|
61
|
+
}
|
|
62
|
+
inFlightRef.current = true;
|
|
63
|
+
// The engine resolves triggerEvent once the chain finishes, including when an action fails.
|
|
64
|
+
await methods.triggerEvent({
|
|
65
|
+
name: 'onTick',
|
|
66
|
+
event: {
|
|
67
|
+
tick: tickRef.current
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
inFlightRef.current = false;
|
|
71
|
+
scheduleTick();
|
|
72
|
+
}
|
|
73
|
+
function start() {
|
|
74
|
+
if (runningRef.current) return;
|
|
75
|
+
tickRef.current = 0;
|
|
76
|
+
runningRef.current = true;
|
|
77
|
+
scheduleTick();
|
|
78
|
+
}
|
|
79
|
+
function stop() {
|
|
80
|
+
runningRef.current = false;
|
|
81
|
+
clearScheduledTick();
|
|
82
|
+
}
|
|
83
|
+
function toggle() {
|
|
84
|
+
if (runningRef.current) {
|
|
85
|
+
stop();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
start();
|
|
89
|
+
}
|
|
90
|
+
function onVisibilityChange() {
|
|
91
|
+
if (hiddenAndPaused()) {
|
|
92
|
+
clearScheduledTick();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
scheduleTick();
|
|
96
|
+
}
|
|
97
|
+
useEffect(()=>{
|
|
98
|
+
methods.registerMethod('start', start);
|
|
99
|
+
methods.registerMethod('stop', stop);
|
|
100
|
+
methods.registerMethod('toggle', toggle);
|
|
101
|
+
document.addEventListener('visibilitychange', onVisibilityChange);
|
|
102
|
+
if (properties.autoStart === true) {
|
|
103
|
+
start();
|
|
104
|
+
}
|
|
105
|
+
return ()=>{
|
|
106
|
+
document.removeEventListener('visibilitychange', onVisibilityChange);
|
|
107
|
+
stop();
|
|
108
|
+
};
|
|
109
|
+
}, []);
|
|
110
|
+
useEffect(()=>{
|
|
111
|
+
if (timeoutRef.current === null) return;
|
|
112
|
+
if (scheduledIntervalRef.current === properties.interval) return;
|
|
113
|
+
clearScheduledTick();
|
|
114
|
+
scheduleTick();
|
|
115
|
+
}, [
|
|
116
|
+
properties.interval
|
|
117
|
+
]);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
export default withBlockDefaults(PollingTimer);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { createBlockHelper, escapeId } from '@lowdefy/e2e-utils';
|
|
16
|
+
// PollingTimer renders nothing — assert on what onTick does, and use the block wrapper for
|
|
17
|
+
// presence checks.
|
|
18
|
+
const locator = (page, blockId)=>page.locator(`#bl-${escapeId(blockId)}`);
|
|
19
|
+
export default createBlockHelper({
|
|
20
|
+
locator
|
|
21
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Copyright 2020-2026 Lowdefy, Inc
|
|
2
|
+
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
- title: Poll Until a Job Is Done
|
|
16
|
+
blocks:
|
|
17
|
+
- id: job_timer
|
|
18
|
+
type: PollingTimer
|
|
19
|
+
properties:
|
|
20
|
+
interval: 1000
|
|
21
|
+
events:
|
|
22
|
+
onTick:
|
|
23
|
+
# In an app this is a Request that fetches the job status. Here the job
|
|
24
|
+
# reports done on the fifth tick.
|
|
25
|
+
- id: job_timer_set_status
|
|
26
|
+
type: SetState
|
|
27
|
+
params:
|
|
28
|
+
job_status:
|
|
29
|
+
_if:
|
|
30
|
+
test:
|
|
31
|
+
_gte:
|
|
32
|
+
- _event: tick
|
|
33
|
+
- 5
|
|
34
|
+
then: done
|
|
35
|
+
else: running
|
|
36
|
+
- id: job_timer_stop_when_done
|
|
37
|
+
type: CallMethod
|
|
38
|
+
skip:
|
|
39
|
+
_ne:
|
|
40
|
+
- _state: job_status
|
|
41
|
+
- done
|
|
42
|
+
params:
|
|
43
|
+
blockId: job_timer
|
|
44
|
+
method: stop
|
|
45
|
+
- id: job_timer_controls
|
|
46
|
+
type: Box
|
|
47
|
+
layout:
|
|
48
|
+
gap: 8
|
|
49
|
+
blocks:
|
|
50
|
+
- id: job_timer_start
|
|
51
|
+
type: Button
|
|
52
|
+
properties:
|
|
53
|
+
title: Start Job
|
|
54
|
+
icon: AiOutlinePlayCircle
|
|
55
|
+
events:
|
|
56
|
+
onClick:
|
|
57
|
+
- id: job_timer_set_running
|
|
58
|
+
type: SetState
|
|
59
|
+
params:
|
|
60
|
+
job_status: running
|
|
61
|
+
- id: job_timer_call_start
|
|
62
|
+
type: CallMethod
|
|
63
|
+
params:
|
|
64
|
+
blockId: job_timer
|
|
65
|
+
method: start
|
|
66
|
+
- id: job_timer_status
|
|
67
|
+
type: Span
|
|
68
|
+
properties:
|
|
69
|
+
content:
|
|
70
|
+
_nunjucks:
|
|
71
|
+
template: 'Job status: {{ status }}'
|
|
72
|
+
on:
|
|
73
|
+
status:
|
|
74
|
+
_if_none:
|
|
75
|
+
- _state: job_status
|
|
76
|
+
- not started
|
|
77
|
+
|
|
78
|
+
- title: Start and Stop
|
|
79
|
+
blocks:
|
|
80
|
+
- id: manual_timer
|
|
81
|
+
type: PollingTimer
|
|
82
|
+
properties:
|
|
83
|
+
interval: 1000
|
|
84
|
+
events:
|
|
85
|
+
onTick:
|
|
86
|
+
- id: manual_timer_set_tick
|
|
87
|
+
type: SetState
|
|
88
|
+
params:
|
|
89
|
+
manual_ticks:
|
|
90
|
+
_event: tick
|
|
91
|
+
- id: manual_timer_controls
|
|
92
|
+
type: Box
|
|
93
|
+
layout:
|
|
94
|
+
gap: 8
|
|
95
|
+
blocks:
|
|
96
|
+
- id: manual_timer_start
|
|
97
|
+
type: Button
|
|
98
|
+
properties:
|
|
99
|
+
title: Start
|
|
100
|
+
icon: AiOutlinePlayCircle
|
|
101
|
+
events:
|
|
102
|
+
onClick:
|
|
103
|
+
- id: manual_timer_call_start
|
|
104
|
+
type: CallMethod
|
|
105
|
+
params:
|
|
106
|
+
blockId: manual_timer
|
|
107
|
+
method: start
|
|
108
|
+
- id: manual_timer_stop
|
|
109
|
+
type: Button
|
|
110
|
+
properties:
|
|
111
|
+
title: Stop
|
|
112
|
+
icon: AiOutlinePauseCircle
|
|
113
|
+
events:
|
|
114
|
+
onClick:
|
|
115
|
+
- id: manual_timer_call_stop
|
|
116
|
+
type: CallMethod
|
|
117
|
+
params:
|
|
118
|
+
blockId: manual_timer
|
|
119
|
+
method: stop
|
|
120
|
+
- id: manual_timer_toggle
|
|
121
|
+
type: Button
|
|
122
|
+
properties:
|
|
123
|
+
title: Toggle
|
|
124
|
+
icon: AiOutlineSwap
|
|
125
|
+
events:
|
|
126
|
+
onClick:
|
|
127
|
+
- id: manual_timer_call_toggle
|
|
128
|
+
type: CallMethod
|
|
129
|
+
params:
|
|
130
|
+
blockId: manual_timer
|
|
131
|
+
method: toggle
|
|
132
|
+
- id: manual_timer_ticks
|
|
133
|
+
type: Span
|
|
134
|
+
properties:
|
|
135
|
+
content:
|
|
136
|
+
_nunjucks:
|
|
137
|
+
template: 'Ticks: {{ ticks }}'
|
|
138
|
+
on:
|
|
139
|
+
ticks:
|
|
140
|
+
_state: manual_ticks
|
|
141
|
+
|
|
142
|
+
- title: Auto Start with a Tick Limit
|
|
143
|
+
blocks:
|
|
144
|
+
- id: limited_timer
|
|
145
|
+
type: PollingTimer
|
|
146
|
+
properties:
|
|
147
|
+
interval: 1000
|
|
148
|
+
autoStart: true
|
|
149
|
+
maxTicks: 5
|
|
150
|
+
events:
|
|
151
|
+
onTick:
|
|
152
|
+
- id: limited_timer_set_tick
|
|
153
|
+
type: SetState
|
|
154
|
+
params:
|
|
155
|
+
limited_ticks:
|
|
156
|
+
_event: tick
|
|
157
|
+
- id: limited_timer_ticks
|
|
158
|
+
type: Span
|
|
159
|
+
properties:
|
|
160
|
+
content:
|
|
161
|
+
_nunjucks:
|
|
162
|
+
template: 'Ticks: {{ ticks }} of 5'
|
|
163
|
+
on:
|
|
164
|
+
ticks:
|
|
165
|
+
_state: limited_ticks
|
|
166
|
+
|
|
167
|
+
- title: Keep Ticking in a Hidden Tab
|
|
168
|
+
blocks:
|
|
169
|
+
- id: background_timer
|
|
170
|
+
type: PollingTimer
|
|
171
|
+
properties:
|
|
172
|
+
interval: 2000
|
|
173
|
+
autoStart: true
|
|
174
|
+
pauseWhenHidden: false
|
|
175
|
+
events:
|
|
176
|
+
onTick:
|
|
177
|
+
- id: background_timer_set_tick
|
|
178
|
+
type: SetState
|
|
179
|
+
params:
|
|
180
|
+
background_ticks:
|
|
181
|
+
_event: tick
|
|
182
|
+
- id: background_timer_ticks
|
|
183
|
+
type: Span
|
|
184
|
+
properties:
|
|
185
|
+
content:
|
|
186
|
+
_nunjucks:
|
|
187
|
+
template: 'Ticks while this tab was open or hidden: {{ ticks }}'
|
|
188
|
+
on:
|
|
189
|
+
ticks:
|
|
190
|
+
_state: background_ticks
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ export default {
|
|
16
|
+
category: 'display',
|
|
17
|
+
icons: [],
|
|
18
|
+
valueType: null,
|
|
19
|
+
events: {
|
|
20
|
+
onTick: {
|
|
21
|
+
description: 'Trigger actions on every tick while the timer is running. The next tick is scheduled `interval` ms after the `onTick` actions finish, so ticks never overlap.',
|
|
22
|
+
event: {
|
|
23
|
+
tick: 'The number of ticks since the timer was started, starting at 1.'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
methods: {
|
|
28
|
+
start: 'Start the timer and reset the tick count to zero. The first tick fires after `interval` ms. Does nothing if the timer is already running.',
|
|
29
|
+
stop: 'Stop the timer. If `onTick` actions are running, they finish but no further tick is scheduled. Does nothing if the timer is not running.',
|
|
30
|
+
toggle: 'Start the timer if it is stopped, stop it if it is running.'
|
|
31
|
+
},
|
|
32
|
+
properties: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
properties: {
|
|
36
|
+
interval: {
|
|
37
|
+
type: 'integer',
|
|
38
|
+
minimum: 1,
|
|
39
|
+
description: 'Milliseconds to wait after the `onTick` actions of one tick finish before the next tick fires. Changing it while the timer is waiting restarts the wait.'
|
|
40
|
+
},
|
|
41
|
+
autoStart: {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
default: false,
|
|
44
|
+
description: 'Start ticking when the block mounts. Off by default, so the timer is started with the `start` method.'
|
|
45
|
+
},
|
|
46
|
+
maxTicks: {
|
|
47
|
+
type: 'integer',
|
|
48
|
+
minimum: 1,
|
|
49
|
+
description: 'Stop the timer after this many ticks. The `onTick` event of the last tick is still triggered. Unlimited by default.'
|
|
50
|
+
},
|
|
51
|
+
pauseWhenHidden: {
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
default: true,
|
|
54
|
+
description: 'Pause ticking while the browser tab is hidden (`document.visibilityState`), and resume when it becomes visible again. Ticks missed while hidden are not fired on resume; the next tick fires `interval` ms after the tab is visible.'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
required: [
|
|
58
|
+
'interval'
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/ import { type } from '@lowdefy/helpers';
|
|
16
|
+
function validateInterval({ interval }) {
|
|
17
|
+
if (!type.isInt(interval) || interval < 1) {
|
|
18
|
+
throw new Error('PollingTimer "interval" should be a positive integer of milliseconds.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default validateInterval;
|
package/dist/blocks.js
CHANGED
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ export { default as Anchor } from './blocks/Anchor/Anchor.js';
|
|
16
16
|
export { default as Box } from './blocks/Box/Box.js';
|
|
17
|
+
export { default as ClickableHtml } from './blocks/ClickableHtml/ClickableHtml.js';
|
|
17
18
|
export { default as DangerousHtml } from './blocks/DangerousHtml/DangerousHtml.js';
|
|
18
19
|
export { default as Dynamic } from './blocks/Dynamic/Dynamic.js';
|
|
19
20
|
export { default as Html } from './blocks/Html/Html.js';
|
|
20
21
|
export { default as Icon } from './blocks/Icon/Icon.js';
|
|
21
22
|
export { default as Img } from './blocks/Img/Img.js';
|
|
22
23
|
export { default as List } from './blocks/List/List.js';
|
|
24
|
+
export { default as PollingTimer } from './blocks/PollingTimer/PollingTimer.js';
|
|
23
25
|
export { default as Span } from './blocks/Span/Span.js';
|
|
24
26
|
export { default as Throw } from './blocks/Throw/Throw.js';
|
package/dist/e2e.js
CHANGED
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ export { default as Anchor } from './blocks/Anchor/e2e.js';
|
|
16
16
|
export { default as Box } from './blocks/Box/e2e.js';
|
|
17
|
+
export { default as ClickableHtml } from './blocks/ClickableHtml/e2e.js';
|
|
17
18
|
export { default as DangerousHtml } from './blocks/DangerousHtml/e2e.js';
|
|
18
19
|
export { default as Dynamic } from './blocks/Dynamic/e2e.js';
|
|
19
20
|
export { default as Html } from './blocks/Html/e2e.js';
|
|
20
21
|
export { default as Icon } from './blocks/Icon/e2e.js';
|
|
21
22
|
export { default as Img } from './blocks/Img/e2e.js';
|
|
22
23
|
export { default as List } from './blocks/List/e2e.js';
|
|
24
|
+
export { default as PollingTimer } from './blocks/PollingTimer/e2e.js';
|
|
23
25
|
export { default as Span } from './blocks/Span/e2e.js';
|
|
24
26
|
export { default as Throw } from './blocks/Throw/e2e.js';
|
package/dist/metas.js
CHANGED
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/ export { default as Anchor } from './blocks/Anchor/meta.js';
|
|
16
16
|
export { default as Box } from './blocks/Box/meta.js';
|
|
17
|
+
export { default as ClickableHtml } from './blocks/ClickableHtml/meta.js';
|
|
17
18
|
export { default as DangerousHtml } from './blocks/DangerousHtml/meta.js';
|
|
18
19
|
export { default as Dynamic } from './blocks/Dynamic/meta.js';
|
|
19
20
|
export { default as Html } from './blocks/Html/meta.js';
|
|
20
21
|
export { default as Icon } from './blocks/Icon/meta.js';
|
|
21
22
|
export { default as Img } from './blocks/Img/meta.js';
|
|
22
23
|
export { default as List } from './blocks/List/meta.js';
|
|
24
|
+
export { default as PollingTimer } from './blocks/PollingTimer/meta.js';
|
|
23
25
|
export { default as Span } from './blocks/Span/meta.js';
|
|
24
26
|
export { default as Throw } from './blocks/Throw/meta.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/blocks-basic",
|
|
3
|
-
"version": "0.0.0-experimental-
|
|
3
|
+
"version": "0.0.0-experimental-20260925210809",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Basic html Lowdefy blocks.",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"url": "https://github.com/lowdefy/lowdefy.git"
|
|
32
32
|
},
|
|
33
33
|
"type": "module",
|
|
34
|
+
"sideEffects": [
|
|
35
|
+
"**/*.css"
|
|
36
|
+
],
|
|
34
37
|
"exports": {
|
|
35
38
|
"./*": "./dist/*",
|
|
36
39
|
"./blocks": "./dist/blocks.js",
|
|
@@ -42,17 +45,17 @@
|
|
|
42
45
|
"dist/*"
|
|
43
46
|
],
|
|
44
47
|
"dependencies": {
|
|
45
|
-
"@lowdefy/block-utils": "0.0.0-experimental-
|
|
46
|
-
"@lowdefy/helpers": "0.0.0-experimental-
|
|
47
|
-
"dompurify": "3.4.
|
|
48
|
+
"@lowdefy/block-utils": "0.0.0-experimental-20260925210809",
|
|
49
|
+
"@lowdefy/helpers": "0.0.0-experimental-20260925210809",
|
|
50
|
+
"dompurify": "3.4.13"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
50
53
|
"react": ">=18",
|
|
51
54
|
"react-dom": ">=18"
|
|
52
55
|
},
|
|
53
56
|
"devDependencies": {
|
|
54
|
-
"@lowdefy/block-dev-e2e": "0.0.0-experimental-
|
|
55
|
-
"@lowdefy/e2e-utils": "0.0.0-experimental-
|
|
57
|
+
"@lowdefy/block-dev-e2e": "0.0.0-experimental-20260925210809",
|
|
58
|
+
"@lowdefy/e2e-utils": "0.0.0-experimental-20260925210809",
|
|
56
59
|
"@playwright/test": "1.59.1",
|
|
57
60
|
"@swc/cli": "0.8.1",
|
|
58
61
|
"@swc/core": "1.15.32",
|