@lowdefy/blocks-basic 0.0.0-experimental-20260914131506 → 0.0.0-experimental-20260925135632

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.
@@ -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
+ */ import React from 'react';
16
+ import { withBlockDefaults, HtmlComponent } from '@lowdefy/block-utils';
17
+ // dataset keys are camelCase (data-record-id → recordId); config authors
18
+ // write the attributes in kebab-case, so hand them back as snake_case.
19
+ function toSnakeCase(key) {
20
+ return key.replace(/[A-Z]/g, (match)=>`_${match.toLowerCase()}`);
21
+ }
22
+ const ClickableHtml = ({ blockId, classNames, events, properties, methods, styles })=>{
23
+ // Each clickable element names the event it fires in its data-event
24
+ // attribute (data-event="onEditClick" → events.onEditClick), so every
25
+ // target in the markup has its own action chain. Its other data-*
26
+ // attributes are the event object.
27
+ function onClick(clickEvent) {
28
+ const target = clickEvent.target.closest('[data-event]');
29
+ if (!target || !clickEvent.currentTarget.contains(target)) return;
30
+ const { event: name, ...data } = target.dataset;
31
+ if (!name) return;
32
+ clickEvent.preventDefault();
33
+ const event = {};
34
+ Object.keys(data).forEach((key)=>{
35
+ event[toSnakeCase(key)] = data[key];
36
+ });
37
+ methods.triggerEvent({
38
+ name,
39
+ event
40
+ });
41
+ }
42
+ return /*#__PURE__*/ React.createElement(HtmlComponent, {
43
+ div: true,
44
+ events: events,
45
+ html: properties.html,
46
+ id: blockId,
47
+ methods: methods,
48
+ className: classNames?.element,
49
+ style: styles?.element,
50
+ onClick: onClick
51
+ });
52
+ };
53
+ 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,113 @@
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
@@ -0,0 +1,48 @@
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
+ // Each clickable element names its event in a data-event attribute, so the
30
+ // event names this block fires are authored in its html.
31
+ dynamicEvents: true,
32
+ events: {
33
+ onTextSelection: 'Trigger action when text is selected and pass selected text to the event object.'
34
+ },
35
+ properties: {
36
+ type: 'object',
37
+ additionalProperties: false,
38
+ properties: {
39
+ html: {
40
+ type: 'string',
41
+ 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" }.',
42
+ docs: {
43
+ displayType: 'text-area'
44
+ }
45
+ }
46
+ }
47
+ }
48
+ };
@@ -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
@@ -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-20260914131506",
3
+ "version": "0.0.0-experimental-20260925135632",
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-20260914131506",
46
- "@lowdefy/helpers": "0.0.0-experimental-20260914131506",
47
- "dompurify": "3.4.2"
48
+ "@lowdefy/block-utils": "0.0.0-experimental-20260925135632",
49
+ "@lowdefy/helpers": "0.0.0-experimental-20260925135632",
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-20260914131506",
55
- "@lowdefy/e2e-utils": "0.0.0-experimental-20260914131506",
57
+ "@lowdefy/block-dev-e2e": "0.0.0-experimental-20260925135632",
58
+ "@lowdefy/e2e-utils": "0.0.0-experimental-20260925135632",
56
59
  "@playwright/test": "1.59.1",
57
60
  "@swc/cli": "0.8.1",
58
61
  "@swc/core": "1.15.32",