HS: fix empty/halt/morph/reset/dialog — 17 upstream tests pass
- parser `empty` no-target → (ref "me") (was bogus (sym "me")) - parser `halt` modes distinguish: "all"/"bubbling"/"default" halt execution (raise hs-return), "the-event"/"the event's" only stop propagation/default. "'s" now matched as op token, not keyword. - parser `get` cmd: dispatch + cmd-kw list + parse-get-cmd (parses expr with optional `as TYPE`). Required for `get result as JSON` in fetch chains. - compiler empty-target for (local X): emit (set! X (hs-empty-like X)) so arrays/sets/maps clear the variable, not call DOM empty on the value. - runtime hs-empty-like: container-of-same-type empty value. - runtime hs-empty-target!: drop dead FORM branch that was short-circuiting to innerHTML=""; the querySelectorAll-over-inputs branch now runs. - runtime hs-halt!: take ev param (was free `event` lookup); raise hs-return to stop execution unless mode is "the-event". - runtime hs-reset!: type-aware — FORM → reset, INPUT/TEXTAREA → value/checked from defaults, SELECT → defaultSelected option. - runtime hs-open!/hs-close!: toggle `open` attribute on details elements (not just the prop) so dom-has-attr? assertions work. - runtime hs-coerce JSON: json-stringify dict/list (was str). - test-runner mock: host-get on List + "length"/"size" (was only Dict); dom-set-attr tracks defaultChecked / defaultSelected / defaultValue; mock_query_all supports comma-separated selector groups. - generator: emit boolean attrs (checked/selected/etc) even with null value; drop overcautious "skip HS with bare quotes or embedded HTML" guard so morph tests (source contains embedded <div>) emit properly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -227,12 +227,15 @@ def parse_html(html):
|
||||
'attrs': {}, 'inner': '', 'depth': len(stack),
|
||||
'children': [], 'parent_idx': None
|
||||
}
|
||||
BOOL_ATTRS = {'checked', 'selected', 'disabled', 'multiple',
|
||||
'required', 'readonly', 'autofocus', 'hidden', 'open'}
|
||||
for name, val in attrs:
|
||||
if name == 'id': el['id'] = val
|
||||
elif name == 'class': el['classes'] = (val or '').split()
|
||||
elif name == '_': el['hs'] = val
|
||||
elif name == 'style': el['attrs']['style'] = val or ''
|
||||
elif val is not None: el['attrs'][name] = val
|
||||
elif name in BOOL_ATTRS: el['attrs'][name] = ''
|
||||
# Track parent-child relationship
|
||||
if stack:
|
||||
parent = stack[-1]
|
||||
@@ -483,7 +486,23 @@ def make_ref_fn(elements, var_names, action_str=''):
|
||||
# most likely refers to an *other* element (often the ID'd one).
|
||||
action_uses_alias = any(n not in tags for n in action_vars)
|
||||
|
||||
# Build var→element lookup for depth checks
|
||||
var_to_el = {var_names[i]: elements[i] for i in range(len(var_names))}
|
||||
|
||||
def ref(name):
|
||||
# Special case for `d1`, `d2`, ... (upstream convention `var d1 = make(HTML)`
|
||||
# binds to the outermost wrapper). If the HTML also has an element with
|
||||
# id='d1' *nested inside* the wrapper, the JS variable shadows it — so
|
||||
# `d1.click()` / `d1.innerHTML` in the check refer to the wrapper, not
|
||||
# the nested element. Prefer the top-level positional element here.
|
||||
pos_match = re.match(r'^d(\d+)$', name)
|
||||
if pos_match and name in id_to_var:
|
||||
id_el = var_to_el.get(id_to_var[name])
|
||||
if id_el is not None and id_el.get('depth', 0) > 0:
|
||||
idx = int(pos_match.group(1)) - 1
|
||||
if 0 <= idx < len(top_level_vars):
|
||||
return top_level_vars[idx]
|
||||
|
||||
# Exact ID match first
|
||||
if name in id_to_var:
|
||||
return id_to_var[name]
|
||||
@@ -499,8 +518,13 @@ def make_ref_fn(elements, var_names, action_str=''):
|
||||
return tag_to_id[name]
|
||||
if name in tag_to_unnamed:
|
||||
return tag_to_unnamed[name]
|
||||
# Fallback: first element of that tag (even if named)
|
||||
return tag_to_all.get(name, [first_var])[0]
|
||||
if name in tag_to_all and tag_to_all[name]:
|
||||
# Static element of that tag exists — use it
|
||||
return tag_to_all[name][0]
|
||||
# No static element of this tag: it must be dynamically inserted
|
||||
# by the hyperscript (e.g. `button` after the handler creates one).
|
||||
# Query the DOM at action/check time with a tag selector.
|
||||
return f'(dom-query "{name}")'
|
||||
|
||||
# Tag + number: div1→1st div, div2→2nd div, form1→1st form, etc.
|
||||
m = re.match(r'^([a-z]+)(\d+)$', name)
|
||||
@@ -791,8 +815,6 @@ def emit_element_setup(lines, elements, var_names, root='(dom-body)', indent='
|
||||
hs_val = process_hs_val(el['hs'])
|
||||
if not hs_val:
|
||||
pass # no HS to set
|
||||
elif hs_val.startswith('"') or (hs_val.endswith('"') and '<' in hs_val):
|
||||
lines.append(f'{indent};; HS source has bare quotes or embedded HTML')
|
||||
else:
|
||||
hs_escaped = hs_val.replace('\\', '\\\\').replace('"', '\\"')
|
||||
lines.append(f'{indent}(dom-set-attr {var} "_" "{hs_escaped}")')
|
||||
@@ -1416,7 +1438,14 @@ output.append(' (fn (src)')
|
||||
output.append(' (let ((sx (hs-to-sx (hs-compile src))))')
|
||||
output.append(' (let ((handler (eval-expr-cek')
|
||||
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
|
||||
output.append(' (handler nil)))))')
|
||||
output.append(' (guard')
|
||||
output.append(' (_e')
|
||||
output.append(' (true')
|
||||
output.append(' (if')
|
||||
output.append(' (and (list? _e) (= (first _e) "hs-return"))')
|
||||
output.append(' (nth _e 1)')
|
||||
output.append(' (raise _e))))')
|
||||
output.append(' (handler nil))))))')
|
||||
output.append('')
|
||||
output.append(';; Evaluate with a specific me value (for "I am between" etc.)')
|
||||
output.append('(define eval-hs-with-me')
|
||||
@@ -1424,7 +1453,14 @@ output.append(' (fn (src me-val)')
|
||||
output.append(' (let ((sx (hs-to-sx (hs-compile src))))')
|
||||
output.append(' (let ((handler (eval-expr-cek')
|
||||
output.append(' (list (quote fn) (list (quote me)) (list (quote let) (list (list (quote it) nil) (list (quote event) nil)) sx)))))')
|
||||
output.append(' (handler me-val)))))')
|
||||
output.append(' (guard')
|
||||
output.append(' (_e')
|
||||
output.append(' (true')
|
||||
output.append(' (if')
|
||||
output.append(' (and (list? _e) (= (first _e) "hs-return"))')
|
||||
output.append(' (nth _e 1)')
|
||||
output.append(' (raise _e))))')
|
||||
output.append(' (handler me-val))))))')
|
||||
output.append('')
|
||||
|
||||
# Group by category
|
||||
|
||||
Reference in New Issue
Block a user