mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-14 23:38:57 +00:00
improve form element support
This commit is contained in:
@@ -53,3 +53,76 @@
|
||||
const buttonInvalidFormAttr = $('#button_invalid_form_attr')
|
||||
testing.expectEqual(null, buttonInvalidFormAttr.form)
|
||||
</script>
|
||||
|
||||
<button id="named1" name="submit-btn"></button>
|
||||
<button id="named2"></button>
|
||||
|
||||
<button id="required1" required></button>
|
||||
<button id="required2"></button>
|
||||
|
||||
<script id="name_initial">
|
||||
testing.expectEqual('submit-btn', $('#named1').name)
|
||||
testing.expectEqual('', $('#named2').name)
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const button = document.createElement('button')
|
||||
testing.expectEqual('', button.name)
|
||||
|
||||
button.name = 'action'
|
||||
testing.expectEqual('action', button.name)
|
||||
testing.expectEqual('action', button.getAttribute('name'))
|
||||
|
||||
button.name = 'submit'
|
||||
testing.expectEqual('submit', button.name)
|
||||
testing.expectEqual('submit', button.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_reflects_to_attribute">
|
||||
{
|
||||
const button = document.createElement('button')
|
||||
testing.expectEqual(null, button.getAttribute('name'))
|
||||
|
||||
button.name = 'fieldname'
|
||||
testing.expectEqual('fieldname', button.getAttribute('name'))
|
||||
testing.expectTrue(button.outerHTML.includes('name="fieldname"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_initial">
|
||||
testing.expectEqual(true, $('#required1').required)
|
||||
testing.expectEqual(false, $('#required2').required)
|
||||
</script>
|
||||
|
||||
<script id="required_set">
|
||||
{
|
||||
const button = document.createElement('button')
|
||||
testing.expectEqual(false, button.required)
|
||||
|
||||
button.required = true
|
||||
testing.expectEqual(true, button.required)
|
||||
testing.expectEqual('', button.getAttribute('required'))
|
||||
|
||||
button.required = false
|
||||
testing.expectEqual(false, button.required)
|
||||
testing.expectEqual(null, button.getAttribute('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_reflects_to_attribute">
|
||||
{
|
||||
const button = document.createElement('button')
|
||||
testing.expectEqual(null, button.getAttribute('required'))
|
||||
testing.expectFalse(button.outerHTML.includes('required'))
|
||||
|
||||
button.required = true
|
||||
testing.expectEqual('', button.getAttribute('required'))
|
||||
testing.expectTrue(button.outerHTML.includes('required'))
|
||||
|
||||
button.required = false
|
||||
testing.expectEqual(null, button.getAttribute('required'))
|
||||
testing.expectFalse(button.outerHTML.includes('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<input id="disabled1" type="text" disabled>
|
||||
<input id="disabled2" type="checkbox">
|
||||
|
||||
<!--
|
||||
<script id="type">
|
||||
testing.expectEqual('text', $('#text1').type)
|
||||
testing.expectEqual('text', $('#text2').type)
|
||||
@@ -243,4 +243,109 @@
|
||||
testing.expectEqual('', input.getAttribute('checked'))
|
||||
testing.expectTrue(input.outerHTML.includes('checked'))
|
||||
}
|
||||
</script> -->
|
||||
|
||||
<script id="defaultValue_set">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
testing.expectEqual('', input.defaultValue)
|
||||
testing.expectEqual(null, input.getAttribute('value'))
|
||||
|
||||
input.defaultValue = 'new default'
|
||||
testing.expectEqual('new default', input.defaultValue)
|
||||
testing.expectEqual('new default', input.getAttribute('value'))
|
||||
testing.expectEqual('new default', input.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <script id="defaultChecked_set">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
input.type = 'checkbox'
|
||||
testing.expectEqual(false, input.defaultChecked)
|
||||
testing.expectEqual(null, input.getAttribute('checked'))
|
||||
|
||||
input.defaultChecked = true
|
||||
testing.expectEqual(true, input.defaultChecked)
|
||||
testing.expectEqual('', input.getAttribute('checked'))
|
||||
testing.expectEqual(true, input.checked)
|
||||
|
||||
input.checked = false
|
||||
testing.expectEqual(false, input.checked)
|
||||
testing.expectEqual(true, input.defaultChecked)
|
||||
}
|
||||
</script>
|
||||
|
||||
<input id="named1" type="text" name="username">
|
||||
<input id="named2" type="text">
|
||||
|
||||
<input id="required1" type="text" required>
|
||||
<input id="required2" type="text">
|
||||
|
||||
<script id="name_initial">
|
||||
testing.expectEqual('username', $('#named1').name)
|
||||
testing.expectEqual('', $('#named2').name)
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
testing.expectEqual('', input.name)
|
||||
|
||||
input.name = 'email'
|
||||
testing.expectEqual('email', input.name)
|
||||
testing.expectEqual('email', input.getAttribute('name'))
|
||||
|
||||
input.name = 'password'
|
||||
testing.expectEqual('password', input.name)
|
||||
testing.expectEqual('password', input.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_reflects_to_attribute">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
testing.expectEqual(null, input.getAttribute('name'))
|
||||
|
||||
input.name = 'fieldname'
|
||||
testing.expectEqual('fieldname', input.getAttribute('name'))
|
||||
testing.expectTrue(input.outerHTML.includes('name="fieldname"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_initial">
|
||||
testing.expectEqual(true, $('#required1').required)
|
||||
testing.expectEqual(false, $('#required2').required)
|
||||
</script>
|
||||
|
||||
<script id="required_set">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
testing.expectEqual(false, input.required)
|
||||
|
||||
input.required = true
|
||||
testing.expectEqual(true, input.required)
|
||||
testing.expectEqual('', input.getAttribute('required'))
|
||||
|
||||
input.required = false
|
||||
testing.expectEqual(false, input.required)
|
||||
testing.expectEqual(null, input.getAttribute('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_reflects_to_attribute">
|
||||
{
|
||||
const input = document.createElement('input')
|
||||
testing.expectEqual(null, input.getAttribute('required'))
|
||||
testing.expectFalse(input.outerHTML.includes('required'))
|
||||
|
||||
input.required = true
|
||||
testing.expectEqual('', input.getAttribute('required'))
|
||||
testing.expectTrue(input.outerHTML.includes('required'))
|
||||
|
||||
input.required = false
|
||||
testing.expectEqual(null, input.getAttribute('required'))
|
||||
testing.expectFalse(input.outerHTML.includes('required'))
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
|
||||
@@ -65,3 +65,37 @@
|
||||
$('#opt4').disabled = false
|
||||
testing.expectEqual(false, $('#opt4').disabled)
|
||||
</script>
|
||||
|
||||
<option id="named1" name="choice1">Named option</option>
|
||||
<option id="named2">Unnamed option</option>
|
||||
|
||||
<script id="name_initial">
|
||||
testing.expectEqual('choice1', $('#named1').name)
|
||||
testing.expectEqual('', $('#named2').name)
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const option = document.createElement('option')
|
||||
testing.expectEqual('', option.name)
|
||||
|
||||
option.name = 'opt-name'
|
||||
testing.expectEqual('opt-name', option.name)
|
||||
testing.expectEqual('opt-name', option.getAttribute('name'))
|
||||
|
||||
option.name = 'another-name'
|
||||
testing.expectEqual('another-name', option.name)
|
||||
testing.expectEqual('another-name', option.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_reflects_to_attribute">
|
||||
{
|
||||
const option = document.createElement('option')
|
||||
testing.expectEqual(null, option.getAttribute('name'))
|
||||
|
||||
option.name = 'fieldname'
|
||||
testing.expectEqual('fieldname', option.getAttribute('name'))
|
||||
testing.expectTrue(option.outerHTML.includes('name="fieldname"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -81,3 +81,286 @@
|
||||
const selectNoForm = $('#select_no_form')
|
||||
testing.expectEqual(null, selectNoForm.form)
|
||||
</script>
|
||||
|
||||
<script id="selectedIndex_initial">
|
||||
{
|
||||
testing.expectEqual(2, $('#select1').selectedIndex)
|
||||
testing.expectEqual(0, $('#select2').selectedIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="selectedIndex_set">
|
||||
{
|
||||
$('#select1').selectedIndex = 2
|
||||
testing.expectEqual(2, $('#select1').selectedIndex)
|
||||
testing.expectEqual('val3', $('#select1').value)
|
||||
|
||||
const opt3 = $('#select1').querySelector('option[value="val3"]')
|
||||
testing.expectEqual(true, opt3.selected)
|
||||
|
||||
const opt2 = $('#select1').querySelector('option[value="val2"]')
|
||||
testing.expectEqual(false, opt2.selected)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="selectedIndex_set_negative">
|
||||
{
|
||||
$('#select1').selectedIndex = -1
|
||||
testing.expectEqual(-1, $('#select1').selectedIndex)
|
||||
|
||||
const opt1 = $('#select1').querySelector('option[value="val1"]')
|
||||
const opt2 = $('#select1').querySelector('option[value="val2"]')
|
||||
const opt3 = $('#select1').querySelector('option[value="val3"]')
|
||||
testing.expectEqual(false, opt1.selected)
|
||||
testing.expectEqual(false, opt2.selected)
|
||||
testing.expectEqual(false, opt3.selected)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="selectedIndex_set_out_of_bounds">
|
||||
{
|
||||
$('#select2').selectedIndex = 999
|
||||
const optA = $('#select2').querySelector('option[value="a"]')
|
||||
const optB = $('#select2').querySelector('option[value="b"]')
|
||||
testing.expectEqual(false, optA.selected)
|
||||
testing.expectEqual(false, optB.selected)
|
||||
}
|
||||
</script>
|
||||
|
||||
<select id="select_multi" multiple>
|
||||
<option value="opt1">Option 1</option>
|
||||
<option value="opt2" selected>Option 2</option>
|
||||
<option value="opt3">Option 3</option>
|
||||
</select>
|
||||
|
||||
<script id="multiple_attribute">
|
||||
{
|
||||
const sel = $('#select_multi')
|
||||
testing.expectEqual(true, sel.multiple)
|
||||
testing.expectEqual('opt2', sel.value)
|
||||
testing.expectEqual(1, sel.selectedIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="multiple_setValue">
|
||||
{
|
||||
const sel = $('#select_multi')
|
||||
sel.value = 'opt1'
|
||||
|
||||
const opt1 = sel.querySelector('option[value="opt1"]')
|
||||
const opt2 = sel.querySelector('option[value="opt2"]')
|
||||
testing.expectEqual(true, opt1.selected)
|
||||
testing.expectEqual(false, opt2.selected)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="options_collection">
|
||||
{
|
||||
const sel = $('#select1')
|
||||
const opts = sel.options
|
||||
testing.expectEqual(3, opts.length)
|
||||
testing.expectEqual('HTMLOptionsCollection', opts.constructor.name)
|
||||
|
||||
// Test indexed access
|
||||
testing.expectEqual('val1', opts[0].value)
|
||||
testing.expectEqual('val2', opts[1].value)
|
||||
testing.expectEqual('val3', opts[2].value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="options_selectedIndex">
|
||||
{
|
||||
// Create a fresh select to avoid state from previous tests
|
||||
const sel = document.createElement('select')
|
||||
sel.innerHTML = '<option value="a">A</option><option value="b" selected>B</option><option value="c">C</option>'
|
||||
const opts = sel.options
|
||||
|
||||
// selectedIndex should forward to the select element
|
||||
testing.expectEqual(1, sel.selectedIndex)
|
||||
testing.expectEqual(1, opts.selectedIndex)
|
||||
|
||||
// Setting via options collection should update the select
|
||||
opts.selectedIndex = 2
|
||||
testing.expectEqual(2, sel.selectedIndex)
|
||||
testing.expectEqual(2, opts.selectedIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="options_add_remove">
|
||||
{
|
||||
const sel = document.createElement('select')
|
||||
const opts = sel.options
|
||||
|
||||
testing.expectEqual(0, opts.length)
|
||||
|
||||
// Add an option
|
||||
const opt1 = document.createElement('option')
|
||||
opt1.value = 'a'
|
||||
opt1.textContent = 'Option A'
|
||||
opts.add(opt1, null)
|
||||
testing.expectEqual(1, opts.length)
|
||||
testing.expectEqual('a', opts[0].value)
|
||||
|
||||
// Add another option
|
||||
const opt2 = document.createElement('option')
|
||||
opt2.value = 'b'
|
||||
opt2.textContent = 'Option B'
|
||||
opts.add(opt2, null)
|
||||
testing.expectEqual(2, opts.length)
|
||||
testing.expectEqual('b', opts[1].value)
|
||||
|
||||
// Add an option before the first one
|
||||
const opt0 = document.createElement('option')
|
||||
opt0.value = 'zero'
|
||||
opt0.textContent = 'Option Zero'
|
||||
opts.add(opt0, opt1)
|
||||
testing.expectEqual(3, opts.length)
|
||||
testing.expectEqual('zero', opts[0].value)
|
||||
testing.expectEqual('a', opts[1].value)
|
||||
testing.expectEqual('b', opts[2].value)
|
||||
|
||||
// Remove the middle option (index 1, which is 'a')
|
||||
opts.remove(1)
|
||||
testing.expectEqual(2, opts.length)
|
||||
testing.expectEqual('zero', opts[0].value)
|
||||
testing.expectEqual('b', opts[1].value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="selectedOptions">
|
||||
{
|
||||
const sel = document.createElement('select')
|
||||
sel.multiple = true
|
||||
sel.innerHTML = '<option value="a">A</option><option value="b" selected>B</option><option value="c" selected>C</option><option value="d">D</option>'
|
||||
|
||||
const selectedOpts = sel.selectedOptions
|
||||
testing.expectEqual('HTMLCollection', selectedOpts.constructor.name)
|
||||
testing.expectEqual(2, selectedOpts.length)
|
||||
testing.expectEqual('b', selectedOpts[0].value)
|
||||
testing.expectEqual('c', selectedOpts[1].value)
|
||||
|
||||
// Deselect one
|
||||
sel.options[1].selected = false
|
||||
testing.expectEqual(1, selectedOpts.length)
|
||||
testing.expectEqual('c', selectedOpts[0].value)
|
||||
|
||||
// Select another
|
||||
sel.options[3].selected = true
|
||||
testing.expectEqual(2, selectedOpts.length)
|
||||
testing.expectEqual('c', selectedOpts[0].value)
|
||||
testing.expectEqual('d', selectedOpts[1].value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<select id="named1" name="country"></select>
|
||||
<select id="named2"></select>
|
||||
|
||||
<select id="required1" required></select>
|
||||
<select id="required2"></select>
|
||||
|
||||
<script id="name_initial">
|
||||
testing.expectEqual('country', $('#named1').name)
|
||||
testing.expectEqual('', $('#named2').name)
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual('', select.name)
|
||||
|
||||
select.name = 'choices'
|
||||
testing.expectEqual('choices', select.name)
|
||||
testing.expectEqual('choices', select.getAttribute('name'))
|
||||
|
||||
select.name = 'options'
|
||||
testing.expectEqual('options', select.name)
|
||||
testing.expectEqual('options', select.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_reflects_to_attribute">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual(null, select.getAttribute('name'))
|
||||
|
||||
select.name = 'fieldname'
|
||||
testing.expectEqual('fieldname', select.getAttribute('name'))
|
||||
testing.expectTrue(select.outerHTML.includes('name="fieldname"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_initial">
|
||||
testing.expectEqual(true, $('#required1').required)
|
||||
testing.expectEqual(false, $('#required2').required)
|
||||
</script>
|
||||
|
||||
<script id="required_set">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual(false, select.required)
|
||||
|
||||
select.required = true
|
||||
testing.expectEqual(true, select.required)
|
||||
testing.expectEqual('', select.getAttribute('required'))
|
||||
|
||||
select.required = false
|
||||
testing.expectEqual(false, select.required)
|
||||
testing.expectEqual(null, select.getAttribute('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_reflects_to_attribute">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual(null, select.getAttribute('required'))
|
||||
testing.expectFalse(select.outerHTML.includes('required'))
|
||||
|
||||
select.required = true
|
||||
testing.expectEqual('', select.getAttribute('required'))
|
||||
testing.expectTrue(select.outerHTML.includes('required'))
|
||||
|
||||
select.required = false
|
||||
testing.expectEqual(null, select.getAttribute('required'))
|
||||
testing.expectFalse(select.outerHTML.includes('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<select id="sized1" size="5"></select>
|
||||
<select id="sized2" size=" 932 asd"></select>
|
||||
<select id="sized3" size="93abc"></select>
|
||||
<select id="sized4" size="nope"></select>
|
||||
<select id="sized5"></select>
|
||||
|
||||
<script id="size_initial">
|
||||
testing.expectEqual(5, $('#sized1').size)
|
||||
testing.expectEqual(932, $('#sized2').size)
|
||||
testing.expectEqual(93, $('#sized3').size)
|
||||
testing.expectEqual(0, $('#sized4').size)
|
||||
testing.expectEqual(0, $('#sized5').size)
|
||||
</script>
|
||||
|
||||
<script id="size_set">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual(0, select.size)
|
||||
|
||||
select.size = 10
|
||||
testing.expectEqual(10, select.size)
|
||||
testing.expectEqual('10', select.getAttribute('size'))
|
||||
|
||||
select.size = 42
|
||||
testing.expectEqual(42, select.size)
|
||||
testing.expectEqual('42', select.getAttribute('size'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="size_reflects_to_attribute">
|
||||
{
|
||||
const select = document.createElement('select')
|
||||
testing.expectEqual(null, select.getAttribute('size'))
|
||||
|
||||
select.size = 7
|
||||
testing.expectEqual('7', select.getAttribute('size'))
|
||||
testing.expectTrue(select.outerHTML.includes('size="7"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -76,3 +76,76 @@
|
||||
const textareaInvalidFormAttr = $('#textarea_invalid_form_attr')
|
||||
testing.expectEqual(null, textareaInvalidFormAttr.form)
|
||||
</script>
|
||||
|
||||
<textarea id="named1" name="comments"></textarea>
|
||||
<textarea id="named2"></textarea>
|
||||
|
||||
<textarea id="required1" required></textarea>
|
||||
<textarea id="required2"></textarea>
|
||||
|
||||
<script id="name_initial">
|
||||
testing.expectEqual('comments', $('#named1').name)
|
||||
testing.expectEqual('', $('#named2').name)
|
||||
</script>
|
||||
|
||||
<script id="name_set">
|
||||
{
|
||||
const textarea = document.createElement('textarea')
|
||||
testing.expectEqual('', textarea.name)
|
||||
|
||||
textarea.name = 'message'
|
||||
testing.expectEqual('message', textarea.name)
|
||||
testing.expectEqual('message', textarea.getAttribute('name'))
|
||||
|
||||
textarea.name = 'feedback'
|
||||
testing.expectEqual('feedback', textarea.name)
|
||||
testing.expectEqual('feedback', textarea.getAttribute('name'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="name_reflects_to_attribute">
|
||||
{
|
||||
const textarea = document.createElement('textarea')
|
||||
testing.expectEqual(null, textarea.getAttribute('name'))
|
||||
|
||||
textarea.name = 'fieldname'
|
||||
testing.expectEqual('fieldname', textarea.getAttribute('name'))
|
||||
testing.expectTrue(textarea.outerHTML.includes('name="fieldname"'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_initial">
|
||||
testing.expectEqual(true, $('#required1').required)
|
||||
testing.expectEqual(false, $('#required2').required)
|
||||
</script>
|
||||
|
||||
<script id="required_set">
|
||||
{
|
||||
const textarea = document.createElement('textarea')
|
||||
testing.expectEqual(false, textarea.required)
|
||||
|
||||
textarea.required = true
|
||||
testing.expectEqual(true, textarea.required)
|
||||
testing.expectEqual('', textarea.getAttribute('required'))
|
||||
|
||||
textarea.required = false
|
||||
testing.expectEqual(false, textarea.required)
|
||||
testing.expectEqual(null, textarea.getAttribute('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="required_reflects_to_attribute">
|
||||
{
|
||||
const textarea = document.createElement('textarea')
|
||||
testing.expectEqual(null, textarea.getAttribute('required'))
|
||||
testing.expectFalse(textarea.outerHTML.includes('required'))
|
||||
|
||||
textarea.required = true
|
||||
testing.expectEqual('', textarea.getAttribute('required'))
|
||||
testing.expectTrue(textarea.outerHTML.includes('required'))
|
||||
|
||||
textarea.required = false
|
||||
testing.expectEqual(null, textarea.getAttribute('required'))
|
||||
testing.expectFalse(textarea.outerHTML.includes('required'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -407,6 +407,7 @@ pub fn replaceChildren(self: *Element, nodes: []const Node.NodeOrText, page: *Pa
|
||||
}
|
||||
|
||||
pub fn remove(self: *Element, page: *Page) void {
|
||||
page.domChanged();
|
||||
const node = self.asNode();
|
||||
const parent = node._parent orelse return;
|
||||
page.removeNode(parent, node, .{ .will_be_reconnected = false });
|
||||
|
||||
@@ -20,6 +20,7 @@ pub const NodeLive = @import("collections/node_live.zig").NodeLive;
|
||||
pub const ChildNodes = @import("collections/ChildNodes.zig");
|
||||
pub const DOMTokenList = @import("collections/DOMTokenList.zig");
|
||||
pub const HTMLAllCollection = @import("collections/HTMLAllCollection.zig");
|
||||
pub const HTMLOptionsCollection = @import("collections/HTMLOptionsCollection.zig");
|
||||
|
||||
pub fn registerTypes() []const type {
|
||||
return &.{
|
||||
@@ -31,6 +32,7 @@ pub fn registerTypes() []const type {
|
||||
@import("collections/NodeList.zig").EntryIterator,
|
||||
@import("collections/HTMLAllCollection.zig"),
|
||||
@import("collections/HTMLAllCollection.zig").Iterator,
|
||||
HTMLOptionsCollection,
|
||||
DOMTokenList,
|
||||
DOMTokenList.Iterator,
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ const Mode = enum {
|
||||
tag_name,
|
||||
class_name,
|
||||
child_elements,
|
||||
child_tag,
|
||||
selected_options,
|
||||
};
|
||||
|
||||
const HTMLCollection = @This();
|
||||
@@ -38,6 +40,8 @@ data: union(Mode) {
|
||||
tag_name: NodeLive(.tag_name),
|
||||
class_name: NodeLive(.class_name),
|
||||
child_elements: NodeLive(.child_elements),
|
||||
child_tag: NodeLive(.child_tag),
|
||||
selected_options: NodeLive(.selected_options),
|
||||
},
|
||||
|
||||
pub fn length(self: *HTMLCollection, page: *const Page) u32 {
|
||||
@@ -66,6 +70,8 @@ pub fn iterator(self: *HTMLCollection, page: *Page) !*Iterator {
|
||||
.tag_name => |*impl| .{ .tag_name = impl._tw.clone() },
|
||||
.class_name => |*impl| .{ .class_name = impl._tw.clone() },
|
||||
.child_elements => |*impl| .{ .child_elements = impl._tw.clone() },
|
||||
.child_tag => |*impl| .{ .child_tag = impl._tw.clone() },
|
||||
.selected_options => |*impl| .{ .selected_options = impl._tw.clone() },
|
||||
},
|
||||
}, page);
|
||||
}
|
||||
@@ -78,6 +84,8 @@ pub const Iterator = GenericIterator(struct {
|
||||
tag_name: TreeWalker.FullExcludeSelf,
|
||||
class_name: TreeWalker.FullExcludeSelf,
|
||||
child_elements: TreeWalker.Children,
|
||||
child_tag: TreeWalker.Children,
|
||||
selected_options: TreeWalker.Children,
|
||||
},
|
||||
|
||||
pub fn next(self: *@This(), _: *Page) ?*Element {
|
||||
@@ -86,6 +94,8 @@ pub const Iterator = GenericIterator(struct {
|
||||
.tag_name => |*impl| impl.nextTw(&self.tw.tag_name),
|
||||
.class_name => |*impl| impl.nextTw(&self.tw.class_name),
|
||||
.child_elements => |*impl| impl.nextTw(&self.tw.child_elements),
|
||||
.child_tag => |*impl| impl.nextTw(&self.tw.child_tag),
|
||||
.selected_options => |*impl| impl.nextTw(&self.tw.selected_options),
|
||||
};
|
||||
}
|
||||
}, null);
|
||||
|
||||
106
src/browser/webapi/collections/HTMLOptionsCollection.zig
Normal file
106
src/browser/webapi/collections/HTMLOptionsCollection.zig
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const HTMLCollection = @import("HTMLCollection.zig");
|
||||
const NodeLive = @import("node_live.zig").NodeLive;
|
||||
|
||||
const HTMLOptionsCollection = @This();
|
||||
|
||||
_proto: *HTMLCollection,
|
||||
_select: *@import("../element/html/Select.zig"),
|
||||
|
||||
pub fn deinit(self: *HTMLOptionsCollection) void {
|
||||
const page = Page.current;
|
||||
page._factory.destroy(self);
|
||||
}
|
||||
|
||||
// Forward length to HTMLCollection
|
||||
pub fn length(self: *HTMLOptionsCollection, page: *Page) u32 {
|
||||
return self._proto.length(page);
|
||||
}
|
||||
|
||||
// Forward indexed access to HTMLCollection
|
||||
pub fn getAtIndex(self: *HTMLOptionsCollection, index: usize, page: *Page) ?*Element {
|
||||
return self._proto.getAtIndex(index, page);
|
||||
}
|
||||
|
||||
pub fn getByName(self: *HTMLOptionsCollection, name: []const u8, page: *Page) ?*Element {
|
||||
return self._proto.getByName(name, page);
|
||||
}
|
||||
|
||||
// Forward selectedIndex to the owning select element
|
||||
pub fn getSelectedIndex(self: *const HTMLOptionsCollection) i32 {
|
||||
return self._select.getSelectedIndex();
|
||||
}
|
||||
|
||||
pub fn setSelectedIndex(self: *HTMLOptionsCollection, index: i32) !void {
|
||||
return self._select.setSelectedIndex(index);
|
||||
}
|
||||
|
||||
const Option = @import("../element/html/Option.zig");
|
||||
|
||||
// Add a new option element
|
||||
pub fn add(self: *HTMLOptionsCollection, element: *Option, before: ?*Option, page: *Page) !void {
|
||||
const select_node = self._select.asNode();
|
||||
const element_node = element.asElement().asNode();
|
||||
|
||||
if (before) |before_option| {
|
||||
const before_node = before_option.asElement().asNode();
|
||||
_ = try select_node.insertBefore(element_node, before_node, page);
|
||||
} else {
|
||||
_ = try select_node.appendChild(element_node, page);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove an option element by index
|
||||
pub fn remove(self: *HTMLOptionsCollection, index: i32, page: *Page) void {
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self._proto.getAtIndex(@intCast(index), page)) |element| {
|
||||
element.remove(page);
|
||||
}
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(HTMLOptionsCollection);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "HTMLOptionsCollection";
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
pub const finalizer = HTMLOptionsCollection.deinit;
|
||||
pub const manage = false;
|
||||
};
|
||||
|
||||
pub const length = bridge.accessor(HTMLOptionsCollection.length, null, .{});
|
||||
|
||||
// Indexed access
|
||||
pub const @"[int]" = bridge.indexed(HTMLOptionsCollection.getAtIndex, .{ .null_as_undefined = true });
|
||||
pub const @"[str]" = bridge.namedIndexed(HTMLOptionsCollection.getByName, null, null, .{ .null_as_undefined = true });
|
||||
|
||||
pub const selectedIndex = bridge.accessor(HTMLOptionsCollection.getSelectedIndex, HTMLOptionsCollection.setSelectedIndex, .{});
|
||||
pub const add = bridge.function(HTMLOptionsCollection.add, .{});
|
||||
pub const remove = bridge.function(HTMLOptionsCollection.remove, .{});
|
||||
};
|
||||
@@ -36,6 +36,8 @@ const Mode = enum {
|
||||
tag_name,
|
||||
class_name,
|
||||
child_elements,
|
||||
child_tag,
|
||||
selected_options,
|
||||
};
|
||||
|
||||
const Filters = union(Mode) {
|
||||
@@ -43,6 +45,8 @@ const Filters = union(Mode) {
|
||||
tag_name: String,
|
||||
class_name: []const u8,
|
||||
child_elements,
|
||||
child_tag: Element.Tag,
|
||||
selected_options,
|
||||
|
||||
fn TypeOf(comptime mode: Mode) type {
|
||||
@setEvalBranchQuota(2000);
|
||||
@@ -71,7 +75,7 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
const Filter = Filters.TypeOf(mode);
|
||||
const TW = switch (mode) {
|
||||
.tag, .tag_name, .class_name => TreeWalker.FullExcludeSelf,
|
||||
.child_elements => TreeWalker.Children,
|
||||
.child_elements, .child_tag, .selected_options => TreeWalker.Children,
|
||||
};
|
||||
return struct {
|
||||
_tw: TW,
|
||||
@@ -213,6 +217,16 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
return Selector.classAttributeContains(class_attr, self._filter);
|
||||
},
|
||||
.child_elements => return node._type == .element,
|
||||
.child_tag => {
|
||||
const el = node.is(Element) orelse return false;
|
||||
return el.getTag() == self._filter;
|
||||
},
|
||||
.selected_options => {
|
||||
const el = node.is(Element) orelse return false;
|
||||
const Option = Element.Html.Option;
|
||||
const opt = el.is(Option) orelse return false;
|
||||
return opt.getSelected();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +250,8 @@ pub fn NodeLive(comptime mode: Mode) type {
|
||||
.tag_name => HTMLCollection{ .data = .{ .tag_name = self } },
|
||||
.class_name => HTMLCollection{ .data = .{ .class_name = self } },
|
||||
.child_elements => HTMLCollection{ .data = .{ .child_elements = self } },
|
||||
.child_tag => HTMLCollection{ .data = .{ .child_tag = self } },
|
||||
.selected_options => HTMLCollection{ .data = .{ .selected_options = self } },
|
||||
};
|
||||
return page._factory.create(collection);
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ pub const List = struct {
|
||||
if (is_id) {
|
||||
try page.document._elements_by_id.put(page.arena, entry._value.str(), element);
|
||||
}
|
||||
page.attributeChange(element, result.normalized, value);
|
||||
page.attributeChange(element, result.normalized, entry._value.str());
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,26 @@ pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *const Button) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn setName(self: *Button, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
pub fn getRequired(self: *const Button) bool {
|
||||
return self.asConstElement().getAttributeSafe("required") != null;
|
||||
}
|
||||
|
||||
pub fn setRequired(self: *Button, required: bool, page: *Page) !void {
|
||||
if (required) {
|
||||
try self.asElement().setAttributeSafe("required", "", page);
|
||||
} else {
|
||||
try self.asElement().removeAttribute("required", page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getForm(self: *Button, page: *Page) ?*Form {
|
||||
const element = self.asElement();
|
||||
|
||||
@@ -84,6 +104,8 @@ pub const JsApi = struct {
|
||||
};
|
||||
|
||||
pub const disabled = bridge.accessor(Button.getDisabled, Button.setDisabled, .{});
|
||||
pub const name = bridge.accessor(Button.getName, Button.setName, .{});
|
||||
pub const required = bridge.accessor(Button.getRequired, Button.setRequired, .{});
|
||||
pub const form = bridge.accessor(Button.getForm, null, .{});
|
||||
};
|
||||
|
||||
|
||||
@@ -109,6 +109,10 @@ pub fn getDefaultValue(self: *const Input) []const u8 {
|
||||
return self._default_value orelse "";
|
||||
}
|
||||
|
||||
pub fn setDefaultValue(self: *Input, value: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("value", value, page);
|
||||
}
|
||||
|
||||
pub fn getChecked(self: *const Input) bool {
|
||||
return self._checked;
|
||||
}
|
||||
@@ -126,6 +130,14 @@ pub fn getDefaultChecked(self: *const Input) bool {
|
||||
return self._default_checked;
|
||||
}
|
||||
|
||||
pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
|
||||
if (checked) {
|
||||
try self.asElement().setAttributeSafe("checked", "", page);
|
||||
} else {
|
||||
try self.asElement().removeAttribute("checked", page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getDisabled(self: *const Input) bool {
|
||||
// TODO: Also check for disabled fieldset ancestors
|
||||
// (but not if we're inside a <legend> of that fieldset)
|
||||
@@ -140,6 +152,26 @@ pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *const Input) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn setName(self: *Input, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
pub fn getRequired(self: *const Input) bool {
|
||||
return self.asConstElement().getAttributeSafe("required") != null;
|
||||
}
|
||||
|
||||
pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
|
||||
if (required) {
|
||||
try self.asElement().setAttributeSafe("required", "", page);
|
||||
} else {
|
||||
try self.asElement().removeAttribute("required", page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getForm(self: *Input, page: *Page) ?*Form {
|
||||
const element = self.asElement();
|
||||
|
||||
@@ -218,10 +250,12 @@ pub const JsApi = struct {
|
||||
|
||||
pub const @"type" = bridge.accessor(Input.getType, Input.setType, .{});
|
||||
pub const value = bridge.accessor(Input.getValue, Input.setValue, .{});
|
||||
pub const defaultValue = bridge.accessor(Input.getDefaultValue, null, .{});
|
||||
pub const defaultValue = bridge.accessor(Input.getDefaultValue, Input.setDefaultValue, .{});
|
||||
pub const checked = bridge.accessor(Input.getChecked, Input.setChecked, .{});
|
||||
pub const defaultChecked = bridge.accessor(Input.getDefaultChecked, null, .{});
|
||||
pub const defaultChecked = bridge.accessor(Input.getDefaultChecked, Input.setDefaultChecked, .{});
|
||||
pub const disabled = bridge.accessor(Input.getDisabled, Input.setDisabled, .{});
|
||||
pub const name = bridge.accessor(Input.getName, Input.setName, .{});
|
||||
pub const required = bridge.accessor(Input.getRequired, Input.setRequired, .{});
|
||||
pub const form = bridge.accessor(Input.getForm, null, .{});
|
||||
};
|
||||
|
||||
@@ -249,13 +283,20 @@ pub const Build = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void {
|
||||
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, page: *Page) !void {
|
||||
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return;
|
||||
const self = element.as(Input);
|
||||
switch (attribute) {
|
||||
.type => self._input_type = Type.fromString(value),
|
||||
.value => self._default_value = value,
|
||||
.checked => self._default_checked = true,
|
||||
.checked => {
|
||||
self._default_checked = true;
|
||||
self._checked = true;
|
||||
// If setting a radio button to checked, uncheck others in the group
|
||||
if (self._input_type == .radio) {
|
||||
try self.uncheckRadioGroup(page);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +306,10 @@ pub const Build = struct {
|
||||
switch (attribute) {
|
||||
.type => self._input_type = .text,
|
||||
.value => self._default_value = null,
|
||||
.checked => self._default_checked = false,
|
||||
.checked => {
|
||||
self._default_checked = false;
|
||||
self._checked = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../../js/js.zig");
|
||||
const Page = @import("../../../Page.zig");
|
||||
|
||||
@@ -35,6 +36,9 @@ _disabled: bool = false,
|
||||
pub fn asElement(self: *Option) *Element {
|
||||
return self._proto._proto;
|
||||
}
|
||||
pub fn asConstElement(self: *const Option) *const Element {
|
||||
return self._proto._proto;
|
||||
}
|
||||
pub fn asNode(self: *Option) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
@@ -45,7 +49,7 @@ pub fn getValue(self: *const Option) []const u8 {
|
||||
}
|
||||
|
||||
pub fn setValue(self: *Option, value: []const u8, page: *Page) !void {
|
||||
const owned = try page.arena.dupe(u8, value);
|
||||
const owned = try page.dupeString(value);
|
||||
try self.asElement().setAttributeSafe("value", owned, page);
|
||||
self._value = owned;
|
||||
}
|
||||
@@ -59,10 +63,10 @@ pub fn getSelected(self: *const Option) bool {
|
||||
}
|
||||
|
||||
pub fn setSelected(self: *Option, selected: bool, page: *Page) !void {
|
||||
_ = page;
|
||||
// TODO: When setting selected=true, may need to unselect other options
|
||||
// in the parent <select> if it doesn't have multiple attribute
|
||||
self._selected = selected;
|
||||
page.domChanged();
|
||||
}
|
||||
|
||||
pub fn getDefaultSelected(self: *const Option) bool {
|
||||
@@ -82,6 +86,14 @@ pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *const Option) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn setName(self: *Option, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(Option);
|
||||
|
||||
@@ -96,6 +108,7 @@ pub const JsApi = struct {
|
||||
pub const selected = bridge.accessor(Option.getSelected, Option.setSelected, .{});
|
||||
pub const defaultSelected = bridge.accessor(Option.getDefaultSelected, null, .{});
|
||||
pub const disabled = bridge.accessor(Option.getDisabled, Option.setDisabled, .{});
|
||||
pub const name = bridge.accessor(Option.getName, Option.setName, .{});
|
||||
};
|
||||
|
||||
pub const Build = struct {
|
||||
@@ -126,6 +139,30 @@ pub const Build = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void {
|
||||
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return;
|
||||
const self = element.as(Option);
|
||||
switch (attribute) {
|
||||
.value => self._value = value,
|
||||
.selected => {
|
||||
self._default_selected = true;
|
||||
self._selected = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void {
|
||||
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return;
|
||||
const self = element.as(Option);
|
||||
switch (attribute) {
|
||||
.value => self._value = null,
|
||||
.selected => {
|
||||
self._default_selected = false;
|
||||
self._selected = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const testing = @import("../../../../testing.zig");
|
||||
|
||||
@@ -22,12 +22,14 @@ const Page = @import("../../../Page.zig");
|
||||
const Node = @import("../../Node.zig");
|
||||
const Element = @import("../../Element.zig");
|
||||
const HtmlElement = @import("../Html.zig");
|
||||
const collections = @import("../../collections.zig");
|
||||
const Form = @import("Form.zig");
|
||||
const Option = @import("Option.zig");
|
||||
|
||||
const Select = @This();
|
||||
|
||||
_proto: *HtmlElement,
|
||||
_selected_index_set: bool = false,
|
||||
|
||||
pub fn asElement(self: *Select) *Element {
|
||||
return self._proto._proto;
|
||||
@@ -38,31 +40,22 @@ pub fn asConstElement(self: *const Select) *const Element {
|
||||
pub fn asNode(self: *Select) *Node {
|
||||
return self.asElement().asNode();
|
||||
}
|
||||
pub fn asConstNode(self: *const Select) *const Node {
|
||||
return self.asConstElement().asConstNode();
|
||||
}
|
||||
|
||||
pub fn getValue(self: *Select) []const u8 {
|
||||
// Return value of first selected option, or first option if none selected
|
||||
var first_option: ?*Option = null;
|
||||
var child = self.asNode().firstChild();
|
||||
while (child) |c| {
|
||||
if (c.is(Element)) |el| {
|
||||
switch (el._type) {
|
||||
.html => |html_el| {
|
||||
switch (html_el._type) {
|
||||
.option => |opt| {
|
||||
var iter = self.asNode().childrenIterator();
|
||||
while (iter.next()) |child| {
|
||||
const option = child.is(Option) orelse continue;
|
||||
if (first_option == null) {
|
||||
first_option = opt;
|
||||
first_option = option;
|
||||
}
|
||||
if (opt.getSelected()) {
|
||||
return opt.getValue();
|
||||
if (option.getSelected()) {
|
||||
return option.getValue();
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
child = c.nextSibling();
|
||||
}
|
||||
// No explicitly selected option, return first option's value
|
||||
if (first_option) |opt| {
|
||||
@@ -74,27 +67,65 @@ pub fn getValue(self: *Select) []const u8 {
|
||||
pub fn setValue(self: *Select, value: []const u8, page: *Page) !void {
|
||||
_ = page;
|
||||
// Find option with matching value and select it
|
||||
var child = self.asNode().firstChild();
|
||||
while (child) |c| {
|
||||
if (c.is(Element)) |el| {
|
||||
switch (el._type) {
|
||||
.html => |html_el| {
|
||||
switch (html_el._type) {
|
||||
.option => |opt| {
|
||||
const opt_value = opt.getValue();
|
||||
if (std.mem.eql(u8, opt_value, value)) {
|
||||
opt._selected = true;
|
||||
// Note: This updates the current state (_selected), not the default state (attribute)
|
||||
// Setting value always deselects all others, even for multiple selects
|
||||
var iter = self.asNode().childrenIterator();
|
||||
while (iter.next()) |child| {
|
||||
const option = child.is(Option) orelse continue;
|
||||
option._selected = std.mem.eql(u8, option.getValue(), value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getSelectedIndex(self: *Select) i32 {
|
||||
var index: i32 = 0;
|
||||
var has_options = false;
|
||||
var iter = self.asNode().childrenIterator();
|
||||
while (iter.next()) |child| {
|
||||
const option = child.is(Option) orelse continue;
|
||||
has_options = true;
|
||||
if (option.getSelected()) {
|
||||
return index;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
// If selectedIndex was explicitly set and no option is selected, return -1
|
||||
// If selectedIndex was never set, return 0 (first option implicitly selected) if we have options
|
||||
if (self._selected_index_set) {
|
||||
return -1;
|
||||
}
|
||||
return if (has_options) 0 else -1;
|
||||
}
|
||||
|
||||
pub fn setSelectedIndex(self: *Select, index: i32) !void {
|
||||
// Mark that selectedIndex has been explicitly set
|
||||
self._selected_index_set = true;
|
||||
|
||||
// Select option at given index
|
||||
// Note: This updates the current state (_selected), not the default state (attribute)
|
||||
const is_multiple = self.getMultiple();
|
||||
var current_index: i32 = 0;
|
||||
var iter = self.asNode().childrenIterator();
|
||||
while (iter.next()) |child| {
|
||||
const option = child.is(Option) orelse continue;
|
||||
if (current_index == index) {
|
||||
option._selected = true;
|
||||
} else if (!is_multiple) {
|
||||
// Only deselect others if not multiple
|
||||
option._selected = false;
|
||||
}
|
||||
current_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getMultiple(self: *const Select) bool {
|
||||
return self.asConstElement().getAttributeSafe("multiple") != null;
|
||||
}
|
||||
|
||||
pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
|
||||
if (multiple) {
|
||||
try self.asElement().setAttributeSafe("multiple", "", page);
|
||||
} else {
|
||||
opt._selected = false;
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
child = c.nextSibling();
|
||||
try self.asElement().removeAttribute("multiple", page);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +141,65 @@ pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *const Select) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn setName(self: *Select, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
pub fn getSize(self: *const Select) u32 {
|
||||
const s = self.asConstElement().getAttributeSafe("size") orelse return 0;
|
||||
|
||||
const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace);
|
||||
|
||||
var end: usize = 0;
|
||||
for (trimmed) |b| {
|
||||
if (!std.ascii.isDigit(b)) {
|
||||
break;
|
||||
}
|
||||
end += 1;
|
||||
}
|
||||
if (end == 0) {
|
||||
return 0;
|
||||
}
|
||||
return std.fmt.parseInt(u32, trimmed[0..end], 10) catch 0;
|
||||
}
|
||||
|
||||
pub fn setSize(self: *Select, size: u32, page: *Page) !void {
|
||||
const size_string = try std.fmt.allocPrint(page.call_arena, "{d}", .{size});
|
||||
try self.asElement().setAttributeSafe("size", size_string, page);
|
||||
}
|
||||
|
||||
pub fn getRequired(self: *const Select) bool {
|
||||
return self.asConstElement().getAttributeSafe("required") != null;
|
||||
}
|
||||
|
||||
pub fn setRequired(self: *Select, required: bool, page: *Page) !void {
|
||||
if (required) {
|
||||
try self.asElement().setAttributeSafe("required", "", page);
|
||||
} else {
|
||||
try self.asElement().removeAttribute("required", page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getOptions(self: *Select, page: *Page) !*collections.HTMLOptionsCollection {
|
||||
// For options, we use the child_tag mode to filter only <option> elements
|
||||
const node_live = collections.NodeLive(.child_tag).init(null, self.asNode(), .option, page);
|
||||
const html_collection = try node_live.runtimeGenericWrap(page);
|
||||
|
||||
// Create and return HTMLOptionsCollection
|
||||
return page._factory.create(collections.HTMLOptionsCollection{
|
||||
._proto = html_collection,
|
||||
._select = self,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getSelectedOptions(self: *Select, page: *Page) !collections.NodeLive(.selected_options) {
|
||||
return collections.NodeLive(.selected_options).init(null, self.asNode(), {}, page);
|
||||
}
|
||||
|
||||
pub fn getForm(self: *Select, page: *Page) ?*Form {
|
||||
const element = self.asElement();
|
||||
|
||||
@@ -144,8 +234,15 @@ pub const JsApi = struct {
|
||||
};
|
||||
|
||||
pub const value = bridge.accessor(Select.getValue, Select.setValue, .{});
|
||||
pub const selectedIndex = bridge.accessor(Select.getSelectedIndex, Select.setSelectedIndex, .{});
|
||||
pub const multiple = bridge.accessor(Select.getMultiple, Select.setMultiple, .{});
|
||||
pub const disabled = bridge.accessor(Select.getDisabled, Select.setDisabled, .{});
|
||||
pub const name = bridge.accessor(Select.getName, Select.setName, .{});
|
||||
pub const required = bridge.accessor(Select.getRequired, Select.setRequired, .{});
|
||||
pub const options = bridge.accessor(Select.getOptions, null, .{});
|
||||
pub const selectedOptions = bridge.accessor(Select.getSelectedOptions, null, .{});
|
||||
pub const form = bridge.accessor(Select.getForm, null, .{});
|
||||
pub const size = bridge.accessor(Select.getSize, Select.setSize, .{});
|
||||
};
|
||||
|
||||
pub const Build = struct {
|
||||
|
||||
@@ -65,6 +65,26 @@ pub fn setDisabled(self: *TextArea, disabled: bool, page: *Page) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getName(self: *const TextArea) []const u8 {
|
||||
return self.asConstElement().getAttributeSafe("name") orelse "";
|
||||
}
|
||||
|
||||
pub fn setName(self: *TextArea, name: []const u8, page: *Page) !void {
|
||||
try self.asElement().setAttributeSafe("name", name, page);
|
||||
}
|
||||
|
||||
pub fn getRequired(self: *const TextArea) bool {
|
||||
return self.asConstElement().getAttributeSafe("required") != null;
|
||||
}
|
||||
|
||||
pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void {
|
||||
if (required) {
|
||||
try self.asElement().setAttributeSafe("required", "", page);
|
||||
} else {
|
||||
try self.asElement().removeAttribute("required", page);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getForm(self: *TextArea, page: *Page) ?*Form {
|
||||
const element = self.asElement();
|
||||
|
||||
@@ -101,6 +121,8 @@ pub const JsApi = struct {
|
||||
pub const value = bridge.accessor(TextArea.getValue, TextArea.setValue, .{});
|
||||
pub const defaultValue = bridge.accessor(TextArea.getDefaultValue, null, .{});
|
||||
pub const disabled = bridge.accessor(TextArea.getDisabled, TextArea.setDisabled, .{});
|
||||
pub const name = bridge.accessor(TextArea.getName, TextArea.setName, .{});
|
||||
pub const required = bridge.accessor(TextArea.getRequired, TextArea.setRequired, .{});
|
||||
pub const form = bridge.accessor(TextArea.getForm, null, .{});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user