dars-framework 1.2.3__py3-none-any.whl
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.
- dars/__init__.py +0 -0
- dars/all.py +69 -0
- dars/cli/__init__.py +0 -0
- dars/cli/doctor/__init__.py +1 -0
- dars/cli/doctor/detect.py +154 -0
- dars/cli/doctor/doctor.py +176 -0
- dars/cli/doctor/installers.py +100 -0
- dars/cli/doctor/persist.py +62 -0
- dars/cli/doctor/preflight.py +33 -0
- dars/cli/doctor/ui.py +54 -0
- dars/cli/hot_reload.py +33 -0
- dars/cli/main.py +1107 -0
- dars/cli/preview.py +448 -0
- dars/cli/translations.py +531 -0
- dars/components/__init__.py +0 -0
- dars/components/advanced/__init__.py +8 -0
- dars/components/advanced/accordion.py +26 -0
- dars/components/advanced/card.py +33 -0
- dars/components/advanced/modal.py +45 -0
- dars/components/advanced/navbar.py +44 -0
- dars/components/advanced/table.py +25 -0
- dars/components/advanced/tabs.py +31 -0
- dars/components/basic/__init__.py +34 -0
- dars/components/basic/button.py +55 -0
- dars/components/basic/checkbox.py +35 -0
- dars/components/basic/container.py +29 -0
- dars/components/basic/datepicker.py +139 -0
- dars/components/basic/image.py +36 -0
- dars/components/basic/input.py +57 -0
- dars/components/basic/link.py +31 -0
- dars/components/basic/markdown.py +86 -0
- dars/components/basic/page.py +20 -0
- dars/components/basic/progressbar.py +18 -0
- dars/components/basic/radiobutton.py +35 -0
- dars/components/basic/select.py +82 -0
- dars/components/basic/slider.py +63 -0
- dars/components/basic/spinner.py +12 -0
- dars/components/basic/text.py +23 -0
- dars/components/basic/textarea.py +46 -0
- dars/components/basic/tooltip.py +19 -0
- dars/components/layout/__init__.py +0 -0
- dars/components/layout/anchor.py +13 -0
- dars/components/layout/flex.py +26 -0
- dars/components/layout/grid.py +45 -0
- dars/config.py +134 -0
- dars/core/__init__.py +0 -0
- dars/core/app.py +957 -0
- dars/core/component.py +284 -0
- dars/core/events.py +102 -0
- dars/core/js_bridge.py +99 -0
- dars/core/properties.py +127 -0
- dars/core/state.py +309 -0
- dars/dars_tests/apps_test/health_check.py +56 -0
- dars/dars_tests/run_tests.py +275 -0
- dars/dars_tests/tests/test_advanced_components.py +69 -0
- dars/dars_tests/tests/test_basic_components.py +88 -0
- dars/dars_tests/tests/test_core_and_cli.py +17 -0
- dars/dars_tests/tests/test_layout_components.py +58 -0
- dars/dars_tests/tests/test_version_check.py +21 -0
- dars/docs/__init__.py +0 -0
- dars/docs/app.md +290 -0
- dars/docs/cli.md +80 -0
- dars/docs/components.md +1679 -0
- dars/docs/custom_components.md +30 -0
- dars/docs/events.md +45 -0
- dars/docs/exporters.md +162 -0
- dars/docs/getting_started.md +79 -0
- dars/docs/index.md +18 -0
- dars/docs/scripts.md +593 -0
- dars/docs/state_management.md +57 -0
- dars/exporters/__init__.py +0 -0
- dars/exporters/base.py +96 -0
- dars/exporters/web/OLD/html_css_js_OLD4.py +1538 -0
- dars/exporters/web/OLD/html_css_js_old.py +1406 -0
- dars/exporters/web/OLD/html_css_js_old2.py +1406 -0
- dars/exporters/web/__init__.py +0 -0
- dars/exporters/web/html_css_js.py +2675 -0
- dars/exporters/web/vdom.py +251 -0
- dars/js_lib.py +206 -0
- dars/scripts/__init__.py +0 -0
- dars/scripts/dscript.py +26 -0
- dars/scripts/script.py +39 -0
- dars/security.py +195 -0
- dars/templates/__init__.py +0 -0
- dars/templates/__pycache__/__init__.cpython-311.pyc +0 -0
- dars/templates/examples/README.md +4 -0
- dars/templates/examples/__pycache__/dynamic_event_demo.cpython-311.pyc +0 -0
- dars/templates/examples/advanced/Modal_Demo/advanced_modal_demo.py +275 -0
- dars/templates/examples/advanced/SimpleDashboard/dashboard.py +437 -0
- dars/templates/examples/advanced/SimpleModermWeb/modern_web_app.py +452 -0
- dars/templates/examples/advanced/VariousComponents/all_components_demo.py +87 -0
- dars/templates/examples/advanced/__init__.py +0 -0
- dars/templates/examples/advanced/dState/state_mods_demo.py +68 -0
- dars/templates/examples/basic/Forms/form_components.py +516 -0
- dars/templates/examples/basic/Forms/simple_form.py +379 -0
- dars/templates/examples/basic/HelloWorld/hello_world.py +56 -0
- dars/templates/examples/basic/Layouts/flex_layout_responsive.py +13 -0
- dars/templates/examples/basic/Layouts/grid_layout_responsive.py +12 -0
- dars/templates/examples/basic/Layouts/layout_multipage_demo.py +23 -0
- dars/templates/examples/basic/Multipage/multipage_example.py +67 -0
- dars/templates/examples/basic/PWA/icon-192x192.png +0 -0
- dars/templates/examples/basic/PWA/icon-512x512.png +0 -0
- dars/templates/examples/basic/PWA/pwa_custom_icons.py +33 -0
- dars/templates/examples/basic/__init__.py +0 -0
- dars/templates/examples/demo/__pycache__/complete_app.cpython-311.pyc +0 -0
- dars/templates/examples/demo/complete_app.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/README.md +159 -0
- dars/templates/examples/markdown/MarkdownTemplate/markdown_template.py +21 -0
- dars/templates/examples/markdown/MarkdownTemplate/other_docs.md +1 -0
- dars/templates/examples/markdown/__init__.py +0 -0
- dars/templates/html/__init__.py +0 -0
- dars/version.py +2 -0
- dars_framework-1.2.3.dist-info/METADATA +15 -0
- dars_framework-1.2.3.dist-info/RECORD +118 -0
- dars_framework-1.2.3.dist-info/WHEEL +5 -0
- dars_framework-1.2.3.dist-info/entry_points.txt +2 -0
- dars_framework-1.2.3.dist-info/licenses/LICENSE +21 -0
- dars_framework-1.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VDOM utilities for Dars HTML exporter.
|
|
3
|
+
|
|
4
|
+
This module defines a minimal, modular Virtual DOM representation and a builder
|
|
5
|
+
that converts Dars Components (built-in or user-defined) to a serializable
|
|
6
|
+
VNode tree. It is intentionally patch-agnostic; it only focuses on building
|
|
7
|
+
an accurate snapshot that future patch systems can consume.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
+
|
|
13
|
+
# We only use loose imports to avoid heavy coupling
|
|
14
|
+
try:
|
|
15
|
+
from dars.core.component import Component
|
|
16
|
+
except Exception: # pragma: no cover - defensive import
|
|
17
|
+
Component = Any # type: ignore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class VNode:
|
|
21
|
+
"""A minimal serializable Virtual Node for Dars components."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
type_name: str,
|
|
26
|
+
id: Optional[str],
|
|
27
|
+
key: Optional[str],
|
|
28
|
+
class_name: Optional[str],
|
|
29
|
+
style: Dict[str, Any],
|
|
30
|
+
props: Dict[str, Any],
|
|
31
|
+
events: Optional[Dict[str, Any]],
|
|
32
|
+
children: Optional[List["VNode"]] = None,
|
|
33
|
+
text: Optional[str] = None,
|
|
34
|
+
is_island: bool = False,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.type = type_name
|
|
37
|
+
self.id = id
|
|
38
|
+
self.key = key
|
|
39
|
+
self.class_name = class_name
|
|
40
|
+
self.style = style or {}
|
|
41
|
+
self.props = props or {}
|
|
42
|
+
self.events = events or None
|
|
43
|
+
self.children = children or []
|
|
44
|
+
self.text = text
|
|
45
|
+
self.isIsland = is_island
|
|
46
|
+
|
|
47
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
48
|
+
d: Dict[str, Any] = {
|
|
49
|
+
"type": self.type,
|
|
50
|
+
"id": self.id,
|
|
51
|
+
"key": self.key,
|
|
52
|
+
"class": self.class_name,
|
|
53
|
+
"style": self.style or {},
|
|
54
|
+
"props": self.props or {},
|
|
55
|
+
"events": self.events or None,
|
|
56
|
+
"children": [c.to_dict() for c in (self.children or [])],
|
|
57
|
+
}
|
|
58
|
+
if self.text is not None:
|
|
59
|
+
d["text"] = self.text
|
|
60
|
+
# Siempre incluimos isIsland para que el runtime pueda tomar decisiones
|
|
61
|
+
d["isIsland"] = bool(self.isIsland)
|
|
62
|
+
return d
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class VDomBuilder:
|
|
66
|
+
"""Builds a VNode tree from a Dars Component tree.
|
|
67
|
+
|
|
68
|
+
Notes:
|
|
69
|
+
- Works with built-in and user-defined components alike.
|
|
70
|
+
- For user components that render HTML via `render(exporter)`, we still
|
|
71
|
+
build a structural node capturing props/events. The future patch system
|
|
72
|
+
may treat these as opaque islands unless more granular hooks are added.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, id_provider: Optional[Callable[[Component, str], str]] = None) -> None:
|
|
76
|
+
# id_provider(component, prefix) -> id string
|
|
77
|
+
self.id_provider = id_provider
|
|
78
|
+
|
|
79
|
+
def build(self, component: Component) -> Dict[str, Any]:
|
|
80
|
+
vnode = self._build_vnode(component, path=["0"]) # raíz con path estable
|
|
81
|
+
return vnode.to_dict()
|
|
82
|
+
|
|
83
|
+
# --- internals ---
|
|
84
|
+
def _safe_props(self, component: Component) -> Dict[str, Any]:
|
|
85
|
+
"""Extract props + public attributes into a single serializable mapping.
|
|
86
|
+
|
|
87
|
+
Rules:
|
|
88
|
+
- Start with component.props (if present), but filter out framework-managed fields
|
|
89
|
+
like id, class_name, style, children, events to avoid duplication.
|
|
90
|
+
- Augment with other public attributes from the component instance (e.g.,
|
|
91
|
+
Markdown.dark_theme, Markdown.file_path, CustomComponent.title), skipping
|
|
92
|
+
callables, private names (prefixed with '_'), Component instances and lists of Components.
|
|
93
|
+
- Only include values that are JSON-serializable; otherwise, stringify as fallback.
|
|
94
|
+
"""
|
|
95
|
+
import json
|
|
96
|
+
|
|
97
|
+
result: Dict[str, Any] = {}
|
|
98
|
+
EXCLUDE_KEYS = {
|
|
99
|
+
'id', 'class_name', 'style', 'children', 'events', 'scripts', 'key',
|
|
100
|
+
'props', # avoid nesting component.props inside props
|
|
101
|
+
'rendered_html', # avoid transporting heavy derived HTML payloads
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# 1) Base props from component.props
|
|
105
|
+
try:
|
|
106
|
+
base_props = getattr(component, 'props', {}) or {}
|
|
107
|
+
for k, v in base_props.items():
|
|
108
|
+
if k in EXCLUDE_KEYS:
|
|
109
|
+
continue
|
|
110
|
+
if callable(v):
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
json.dumps(v)
|
|
114
|
+
result[k] = v
|
|
115
|
+
except Exception:
|
|
116
|
+
result[k] = str(v)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
# 2) Additional public attributes from the instance
|
|
121
|
+
try:
|
|
122
|
+
for k, v in vars(component).items():
|
|
123
|
+
if k in EXCLUDE_KEYS:
|
|
124
|
+
continue
|
|
125
|
+
if k in result:
|
|
126
|
+
continue
|
|
127
|
+
if k.startswith('_'):
|
|
128
|
+
continue
|
|
129
|
+
# Skip methods/callables
|
|
130
|
+
if callable(v):
|
|
131
|
+
continue
|
|
132
|
+
# Skip Component instances or lists/tuples of Components
|
|
133
|
+
try:
|
|
134
|
+
if isinstance(v, Component):
|
|
135
|
+
continue
|
|
136
|
+
if isinstance(v, (list, tuple)) and any(isinstance(it, Component) for it in v):
|
|
137
|
+
continue
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
json.dumps(v)
|
|
143
|
+
result[k] = v
|
|
144
|
+
except Exception:
|
|
145
|
+
result[k] = str(v)
|
|
146
|
+
except Exception:
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
def _serialize_events(self, component: Component) -> Optional[Dict[str, Any]]:
|
|
152
|
+
events_payload: Dict[str, Any] = {}
|
|
153
|
+
try:
|
|
154
|
+
events = getattr(component, 'events', {}) or {}
|
|
155
|
+
for ev_name, handler in events.items():
|
|
156
|
+
code = None
|
|
157
|
+
try:
|
|
158
|
+
if hasattr(handler, 'get_code'):
|
|
159
|
+
code = handler.get_code()
|
|
160
|
+
elif isinstance(handler, dict):
|
|
161
|
+
code = handler.get('code') or handler.get('value')
|
|
162
|
+
elif isinstance(handler, str):
|
|
163
|
+
code = handler
|
|
164
|
+
else:
|
|
165
|
+
# fallback best-effort
|
|
166
|
+
code = str(handler)
|
|
167
|
+
except Exception:
|
|
168
|
+
code = None
|
|
169
|
+
if code:
|
|
170
|
+
events_payload[ev_name] = {"type": "inline", "code": code}
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
return events_payload or None
|
|
174
|
+
|
|
175
|
+
def _text_value(self, component: Component) -> Optional[str]:
|
|
176
|
+
# Try extracting a textual value if the component has a primary text prop
|
|
177
|
+
try:
|
|
178
|
+
for cand in ('text', 'content', 'value', 'label'):
|
|
179
|
+
if hasattr(component, cand):
|
|
180
|
+
v = getattr(component, cand)
|
|
181
|
+
if isinstance(v, (str, int, float)):
|
|
182
|
+
return str(v)
|
|
183
|
+
except Exception:
|
|
184
|
+
pass
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
def _build_vnode(self, component: Component, path: list) -> VNode:
|
|
188
|
+
try:
|
|
189
|
+
comp_type = component.__class__.__name__
|
|
190
|
+
except Exception:
|
|
191
|
+
comp_type = 'Component'
|
|
192
|
+
|
|
193
|
+
# Prefer an injected id provider to keep IDs consistent with the HTML output
|
|
194
|
+
comp_id = getattr(component, 'id', None)
|
|
195
|
+
if self.id_provider is not None:
|
|
196
|
+
try:
|
|
197
|
+
# Choose a sensible prefix based on type name (lowercase)
|
|
198
|
+
prefix = (component.__class__.__name__ or 'comp').lower()
|
|
199
|
+
comp_id = self.id_provider(component, prefix=prefix)
|
|
200
|
+
except Exception:
|
|
201
|
+
# fallback to existing id attribute (may be None)
|
|
202
|
+
comp_id = getattr(component, 'id', None)
|
|
203
|
+
|
|
204
|
+
# Props
|
|
205
|
+
safe_props = self._safe_props(component)
|
|
206
|
+
|
|
207
|
+
# Events
|
|
208
|
+
events_payload = self._serialize_events(component)
|
|
209
|
+
|
|
210
|
+
# Children
|
|
211
|
+
children_nodes: List[VNode] = []
|
|
212
|
+
try:
|
|
213
|
+
for idx, child in enumerate(getattr(component, 'children', []) or []):
|
|
214
|
+
if child is None:
|
|
215
|
+
continue
|
|
216
|
+
child_path = path + [str(idx)]
|
|
217
|
+
children_nodes.append(self._build_vnode(child, child_path))
|
|
218
|
+
except Exception:
|
|
219
|
+
children_nodes = []
|
|
220
|
+
|
|
221
|
+
# Text (optional)
|
|
222
|
+
text_value = self._text_value(component)
|
|
223
|
+
|
|
224
|
+
# Heurística para saber si es componente "isla" (custom)
|
|
225
|
+
is_island = False
|
|
226
|
+
try:
|
|
227
|
+
mod = getattr(component.__class__, '__module__', '') or ''
|
|
228
|
+
# Si no pertenece al paquete de componentes built-in, lo tratamos como isla
|
|
229
|
+
if not mod.startswith('dars.components.'):
|
|
230
|
+
is_island = True
|
|
231
|
+
except Exception:
|
|
232
|
+
is_island = False
|
|
233
|
+
|
|
234
|
+
# Clave estable: si no hay id ni key definidos, usamos el path del árbol
|
|
235
|
+
stable_key = getattr(component, 'key', None)
|
|
236
|
+
if not stable_key and not comp_id:
|
|
237
|
+
stable_key = "/".join(path)
|
|
238
|
+
|
|
239
|
+
vnode = VNode(
|
|
240
|
+
type_name=comp_type,
|
|
241
|
+
id=comp_id,
|
|
242
|
+
key=stable_key,
|
|
243
|
+
class_name=getattr(component, 'class_name', None),
|
|
244
|
+
style=getattr(component, 'style', {}) or {},
|
|
245
|
+
props=safe_props,
|
|
246
|
+
events=events_payload,
|
|
247
|
+
children=children_nodes,
|
|
248
|
+
text=text_value,
|
|
249
|
+
is_island=is_island,
|
|
250
|
+
)
|
|
251
|
+
return vnode
|
dars/js_lib.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Embedded dars.min.js content (ESM + global)
|
|
2
|
+
DARS_MIN_JS = """/* Dars minimal runtime: state + DOM updates (ESM + global) */
|
|
3
|
+
const __registry = new Map();
|
|
4
|
+
|
|
5
|
+
function $(id){ return document.getElementById(id) || document.querySelector(`[data-id="${id}"]`) || null; }
|
|
6
|
+
|
|
7
|
+
function registerState(name, cfg){
|
|
8
|
+
if(!name || !cfg || !cfg.id) return;
|
|
9
|
+
const entry = {
|
|
10
|
+
id: cfg.id,
|
|
11
|
+
states: Array.isArray(cfg.states) ? cfg.states.slice() : [],
|
|
12
|
+
current: 0,
|
|
13
|
+
isCustom: !!cfg.isCustom,
|
|
14
|
+
rules: (cfg.rules && typeof cfg.rules === 'object') ? cfg.rules : {},
|
|
15
|
+
defaultIndex: (typeof cfg.defaultIndex === 'number') ? cfg.defaultIndex : 0,
|
|
16
|
+
defaultValue: (cfg.hasOwnProperty('defaultValue') ? cfg.defaultValue : null),
|
|
17
|
+
__defaultSnapshot: null
|
|
18
|
+
};
|
|
19
|
+
__registry.set(name, entry);
|
|
20
|
+
try{
|
|
21
|
+
const el = $(entry.id);
|
|
22
|
+
if(el){
|
|
23
|
+
const attrs = {};
|
|
24
|
+
try{
|
|
25
|
+
for(const a of el.getAttributeNames()) attrs[a] = el.getAttribute(a);
|
|
26
|
+
}catch(_){ }
|
|
27
|
+
entry.__defaultSnapshot = { attrs, html: String(el.innerHTML||'') };
|
|
28
|
+
}
|
|
29
|
+
}catch(_){ }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getState(name){ return __registry.get(name); }
|
|
33
|
+
|
|
34
|
+
function _restoreDefault(id, snap){
|
|
35
|
+
try{
|
|
36
|
+
const el = $(id);
|
|
37
|
+
if(!el || !snap) return;
|
|
38
|
+
// Remove any dynamic event listeners we attached previously
|
|
39
|
+
try{
|
|
40
|
+
if(el.__darsEv){
|
|
41
|
+
for(const t in el.__darsEv){
|
|
42
|
+
const fn = el.__darsEv[t];
|
|
43
|
+
try{ el.removeEventListener(t, fn, true); }catch(_){ }
|
|
44
|
+
try{ el.removeEventListener(t, fn, false); }catch(_){ }
|
|
45
|
+
}
|
|
46
|
+
el.__darsEv = {};
|
|
47
|
+
}
|
|
48
|
+
}catch(_){ }
|
|
49
|
+
try{
|
|
50
|
+
const current = el.getAttributeNames ? el.getAttributeNames() : [];
|
|
51
|
+
for(const n of current){ if(n !== 'id') el.removeAttribute(n); }
|
|
52
|
+
for(const k in snap.attrs){ if(k !== 'id') el.setAttribute(k, snap.attrs[k]); }
|
|
53
|
+
}catch(_){ }
|
|
54
|
+
try{ el.innerHTML = snap.html || ''; }catch(_){ }
|
|
55
|
+
}catch(_){ }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _applyMods(defaultId, mods){
|
|
59
|
+
if(!Array.isArray(mods) || !mods.length) return;
|
|
60
|
+
for(const m of mods){
|
|
61
|
+
try{
|
|
62
|
+
const op = m && m.op;
|
|
63
|
+
if(!op) continue;
|
|
64
|
+
const tid = (m && m.target) ? m.target : defaultId;
|
|
65
|
+
const el = $(tid);
|
|
66
|
+
if(!el) continue;
|
|
67
|
+
if(op === 'inc' || op === 'dec'){
|
|
68
|
+
const prop = m.prop || 'text';
|
|
69
|
+
const by = Number(m.by || (op==='dec'?-1:1));
|
|
70
|
+
if(prop === 'text'){
|
|
71
|
+
const cur = parseFloat(el.textContent||'0') || 0;
|
|
72
|
+
el.textContent = String(cur + by);
|
|
73
|
+
} else {
|
|
74
|
+
const cur = parseFloat(el.getAttribute(prop)||'0') || 0;
|
|
75
|
+
el.setAttribute(prop, String(cur + by));
|
|
76
|
+
}
|
|
77
|
+
} else if(op === 'set'){
|
|
78
|
+
const attrs = m.attrs || {};
|
|
79
|
+
for(const k in attrs){
|
|
80
|
+
try{
|
|
81
|
+
if(k === 'text') { el.textContent = String(attrs[k]); continue; }
|
|
82
|
+
if(k === 'html') { el.innerHTML = String(attrs[k]); continue; }
|
|
83
|
+
if(k.startsWith('on_')){
|
|
84
|
+
const type = k.slice(3);
|
|
85
|
+
const v = attrs[k];
|
|
86
|
+
const codes = [];
|
|
87
|
+
const pushCode = (item)=>{
|
|
88
|
+
if(typeof item === 'string') codes.push(item);
|
|
89
|
+
else if(item && typeof item.code === 'string') codes.push(item.code);
|
|
90
|
+
};
|
|
91
|
+
if(Array.isArray(v)) v.forEach(pushCode);
|
|
92
|
+
else pushCode(v);
|
|
93
|
+
if(codes.length){
|
|
94
|
+
el.__darsEv = el.__darsEv || {};
|
|
95
|
+
if(el.__darsEv[type]){
|
|
96
|
+
try{ el.removeEventListener(type, el.__darsEv[type], true); }catch(_){ }
|
|
97
|
+
try{ el.removeEventListener(type, el.__darsEv[type], false); }catch(_){ }
|
|
98
|
+
}
|
|
99
|
+
const handler = function(ev){
|
|
100
|
+
try{ ev.stopImmediatePropagation(); }catch(_){ }
|
|
101
|
+
try{ ev.stopPropagation(); }catch(_){ }
|
|
102
|
+
try{ ev.preventDefault(); }catch(_){ }
|
|
103
|
+
try{ ev.cancelBubble = true; }catch(_){ }
|
|
104
|
+
let propName = 'on'+type;
|
|
105
|
+
let prevOn = null;
|
|
106
|
+
try{ prevOn = el[propName]; el[propName] = null; }catch(_){ }
|
|
107
|
+
try{ for(const c of codes){ try{ (0,eval)(c); }catch(_){ } } }finally{
|
|
108
|
+
try{ setTimeout(()=>{ try{ el[propName] = prevOn; }catch(_){ } }, 0); }catch(_){ }
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
try{ el.addEventListener(type, handler, { capture: true }); }catch(_){ }
|
|
112
|
+
el.__darsEv[type] = handler;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
el.setAttribute(k, String(attrs[k]));
|
|
117
|
+
}catch(_){ }
|
|
118
|
+
}
|
|
119
|
+
} else if(op === 'toggleClass'){
|
|
120
|
+
const name = m.name || '';
|
|
121
|
+
const on = m.hasOwnProperty('on') ? !!m.on : null;
|
|
122
|
+
if(!name) continue;
|
|
123
|
+
if(on === null){ el.classList.toggle(name); }
|
|
124
|
+
else if(on){ el.classList.add(name); }
|
|
125
|
+
else { el.classList.remove(name); }
|
|
126
|
+
} else if(op === 'appendText'){
|
|
127
|
+
el.textContent = String(el.textContent||'') + String(m.value||'');
|
|
128
|
+
} else if(op === 'prependText'){
|
|
129
|
+
el.textContent = String(m.value||'') + String(el.textContent||'');
|
|
130
|
+
} else if(op === 'call'){
|
|
131
|
+
try{
|
|
132
|
+
const payload = {};
|
|
133
|
+
if (m.name) payload.name = String(m.name);
|
|
134
|
+
if (m.id) payload.id = String(m.id);
|
|
135
|
+
if (m.hasOwnProperty('state')) payload.state = m.state;
|
|
136
|
+
if (m.hasOwnProperty('goto')) payload.goto = m.goto;
|
|
137
|
+
if (!payload.name && !payload.id && defaultId) payload.id = String(defaultId);
|
|
138
|
+
setTimeout(()=>{ try{ change(payload); }catch(_){ } }, 0);
|
|
139
|
+
}catch(_){ }
|
|
140
|
+
}
|
|
141
|
+
}catch(_){ }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function _resolveGoto(cur, goto, statesLen){
|
|
146
|
+
if(goto == null) return cur;
|
|
147
|
+
if(typeof goto === 'number') return goto;
|
|
148
|
+
if(typeof goto === 'string'){
|
|
149
|
+
if(/^[-+]\d+$/.test(goto)){
|
|
150
|
+
const delta = parseInt(goto, 10);
|
|
151
|
+
const next = cur + delta;
|
|
152
|
+
if(statesLen && statesLen > 0){ return Math.max(0, Math.min(statesLen-1, next)); }
|
|
153
|
+
return next;
|
|
154
|
+
}
|
|
155
|
+
const n = parseInt(goto, 10);
|
|
156
|
+
if(!isNaN(n)) return n;
|
|
157
|
+
}
|
|
158
|
+
return cur;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function change(opt){
|
|
162
|
+
if(!opt||!opt.id) return;
|
|
163
|
+
if(opt.useCustomRender && typeof opt.html === 'string'){
|
|
164
|
+
const el = $(opt.id);
|
|
165
|
+
if(!el) return;
|
|
166
|
+
el.innerHTML = opt.html;
|
|
167
|
+
if(typeof window.DarsHydrate === 'function'){ try{ window.DarsHydrate(el); }catch(e){} }
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const name = opt.name || null;
|
|
172
|
+
const st = name ? __registry.get(name) : null;
|
|
173
|
+
let targetState = (typeof opt.state === 'number') ? opt.state : null;
|
|
174
|
+
let goto = (opt.hasOwnProperty('goto') ? opt.goto : null);
|
|
175
|
+
if(st){
|
|
176
|
+
const cur = st.current || 0;
|
|
177
|
+
const len = Array.isArray(st.states) ? st.states.length : 0;
|
|
178
|
+
if(goto !== null){ targetState = _resolveGoto(cur, goto, len); }
|
|
179
|
+
if(targetState === null){ targetState = cur; }
|
|
180
|
+
st.current = targetState;
|
|
181
|
+
const rules = st.rules && st.rules[String(targetState)];
|
|
182
|
+
if(targetState === 0){
|
|
183
|
+
_restoreDefault(st.id, st.__defaultSnapshot);
|
|
184
|
+
if(rules){ try{ console.error('[Dars] Default state (index 0) is immutable. Rules for state 0 are ignored.'); }catch(_){ } }
|
|
185
|
+
} else if(rules){
|
|
186
|
+
if(Array.isArray(rules.mods)){ _applyMods(st.id, rules.mods); }
|
|
187
|
+
if(rules.hasOwnProperty('goto')){
|
|
188
|
+
const nxt = _resolveGoto(st.current, rules.goto, len);
|
|
189
|
+
if(nxt !== st.current){ st.current = nxt; }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try{
|
|
195
|
+
const el = $(opt.id);
|
|
196
|
+
if(!el) return;
|
|
197
|
+
const ev = new CustomEvent('dars:state', { detail: { id: opt.id, state: targetState } });
|
|
198
|
+
el.dispatchEvent(ev);
|
|
199
|
+
}catch(e){ }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const Dars = { registerState, getState, change, $ };
|
|
203
|
+
try { window.Dars = window.Dars || Dars; } catch(_) {}
|
|
204
|
+
export { registerState, getState, change, $ };
|
|
205
|
+
export default Dars;
|
|
206
|
+
"""
|
dars/scripts/__init__.py
ADDED
|
File without changes
|
dars/scripts/dscript.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from .script import Script
|
|
3
|
+
|
|
4
|
+
class dScript(Script):
|
|
5
|
+
"""
|
|
6
|
+
Script that can be defined as inline (JS code in string) or as a reference to an external file.
|
|
7
|
+
Only one of the two must be present.
|
|
8
|
+
"""
|
|
9
|
+
def __init__(self, code: Optional[str] = None, file_path: Optional[str] = None, target_language: str = "javascript", module: bool = False):
|
|
10
|
+
super().__init__(target_language, module=module)
|
|
11
|
+
if (code is None and file_path is None) or (code is not None and file_path is not None):
|
|
12
|
+
raise ValueError("You have to specify only one: 'code' (inline) or 'file_path' (external), but not both.")
|
|
13
|
+
self.code = code
|
|
14
|
+
self.file_path = file_path
|
|
15
|
+
|
|
16
|
+
def get_code(self) -> str:
|
|
17
|
+
if self.code is not None:
|
|
18
|
+
return self.code
|
|
19
|
+
elif self.file_path is not None:
|
|
20
|
+
try:
|
|
21
|
+
with open(self.file_path, 'r') as f:
|
|
22
|
+
return f.read()
|
|
23
|
+
except FileNotFoundError:
|
|
24
|
+
raise FileNotFoundError(f"The script file was not found: {self.file_path}")
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError("No code or file path defined for this dScript.")
|
dars/scripts/script.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
class Script(ABC):
|
|
5
|
+
"""Base class for script definitions"""
|
|
6
|
+
def __init__(self, target_language: str = "javascript", module: bool = False):
|
|
7
|
+
if target_language not in ["javascript", "typescript"]:
|
|
8
|
+
raise ValueError("The target language must be 'javascript' or 'typescript'")
|
|
9
|
+
self.target_language = target_language
|
|
10
|
+
self.module = module
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def get_code(self) -> str:
|
|
14
|
+
"""Returns the script code in the target language"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class InlineScript(Script):
|
|
18
|
+
"""Script defined directly in Python code"""
|
|
19
|
+
def __init__(self, code: str, target_language: str = "javascript", module: bool = False):
|
|
20
|
+
super().__init__(target_language, module=module)
|
|
21
|
+
self.code = code
|
|
22
|
+
|
|
23
|
+
def get_code(self) -> str:
|
|
24
|
+
return self.code
|
|
25
|
+
|
|
26
|
+
class FileScript(Script):
|
|
27
|
+
"""Script loaded from an external file"""
|
|
28
|
+
def __init__(self, file_path: str, target_language: str = "javascript", module: bool = False):
|
|
29
|
+
super().__init__(target_language, module=module)
|
|
30
|
+
self.file_path = file_path
|
|
31
|
+
|
|
32
|
+
def get_code(self) -> str:
|
|
33
|
+
try:
|
|
34
|
+
with open(self.file_path, 'r') as f:
|
|
35
|
+
return f.read()
|
|
36
|
+
except FileNotFoundError:
|
|
37
|
+
raise FileNotFoundError(f"The script file was not found: {self.file_path}")
|
|
38
|
+
|
|
39
|
+
|