about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-01-02 02:13:07 +0100
committerbptato <nincsnevem662@gmail.com>2023-01-02 02:13:07 +0100
commitd9edcbddae8da8c9cfad0c3bbe47eac35b55af99 (patch)
treea604d07b22f7f1d3c0d499400d64dedace8ae82f
parent278e60e1c95069f30adec94362679744b4182251 (diff)
downloadchawan-d9edcbddae8da8c9cfad0c3bbe47eac35b55af99.tar.gz
Add support for <label>
-rw-r--r--src/buffer/buffer.nim204
-rw-r--r--src/html/dom.nim54
2 files changed, 164 insertions, 94 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim
index e07e9146..f7ad6537 100644
--- a/src/buffer/buffer.nim
+++ b/src/buffer/buffer.nim
@@ -248,17 +248,27 @@ func getTitleAttr(node: StyledNode): string =
   #TODO pseudo-elements
 
 const ClickableElements = {
-  TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA
+  TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA, TAG_LABEL
 }
 
 func getClickable(styledNode: StyledNode): Element =
-  if styledNode == nil or styledNode.node == nil:
+  if styledNode == nil:
     return nil
+  var styledNode = styledNode
+  while styledNode.node == nil:
+    styledNode = styledNode.parent
+    if styledNode == nil:
+      return nil
   if styledNode.t == STYLED_ELEMENT:
     let element = Element(styledNode.node)
-    if element.tagType in ClickableElements:
+    if element.tagType in ClickableElements and (element.tagType != TAG_A or HTMLAnchorElement(element).href != ""):
       return element
-  styledNode.node.findAncestor(ClickableElements)
+  while true:
+    result = styledNode.node.findAncestor(ClickableElements)
+    if result == nil:
+      break
+    if result.tagType != TAG_A or HTMLAnchorElement(result).href != "":
+      break
 
 func getClickHover(styledNode: StyledNode): string =
   let clickable = styledNode.getClickable()
@@ -902,101 +912,109 @@ type ClickResult* = object
   readline*: Option[ReadLineResult]
   repaint*: bool
 
-proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} =
-  if buffer.lines.len <= cursory: return
-  let clickable = buffer.getCursorClickable(cursorx, cursory)
-  if clickable != nil:
-    case clickable.tagType
-    of TAG_SELECT:
-      result.repaint = buffer.setFocus(clickable)
-    of TAG_A:
-      result.repaint = buffer.restoreFocus()
-      let url = parseURL(HTMLAnchorElement(clickable).href, clickable.document.baseURL.some)
-      if url.issome:
-        result.open = some(newRequest(url.get, HTTP_GET))
-    of TAG_OPTION:
-      let option = HTMLOptionElement(clickable)
-      let select = option.select
-      if select != nil:
-        if buffer.document.focus == select:
-          # select option
-          if not select.attrb("multiple"):
-            for option in select.options:
-              option.selected = false
-          option.selected = true
-          result.repaint = buffer.restoreFocus()
-        else:
-          # focus on select
-          result.repaint = buffer.setFocus(select)
-    of TAG_BUTTON:
-      let button = HTMLButtonElement(clickable)
-      if button.form != nil:
-        case button.ctype
-        of BUTTON_SUBMIT: result.open = submitForm(button.form, button)
-        of BUTTON_RESET:
-          button.form.reset()
-          result.repaint = true
-          buffer.do_reshape()
-        of BUTTON_BUTTON: discard
-    of TAG_TEXTAREA:
-      result.repaint = buffer.setFocus(clickable)
-      let textarea = HTMLTextAreaElement(clickable)
-      result.readline = some(ReadLineResult(
-        value: textarea.value,
-        area: true
-      ))
-    of TAG_INPUT:
-      result.repaint = buffer.restoreFocus()
-      let input = HTMLInputElement(clickable)
-      case input.inputType
-      of INPUT_SEARCH:
-        result.repaint = buffer.setFocus(input)
-        result.readline = some(ReadLineResult(
-          prompt: "SEARCH: ",
-          value: input.value
-        ))
-      of INPUT_TEXT, INPUT_PASSWORD:
-        result.repaint = buffer.setFocus(input)
-        result.readline = some(ReadLineResult(
-          prompt: "TEXT: ",
-          value: input.value,
-          hide: input.inputType == INPUT_PASSWORD
-        ))
-      of INPUT_FILE:
-        result.repaint = buffer.setFocus(input)
-        var path = if input.file.issome:
-          input.file.get.path.serialize_unicode()
-        else:
-          ""
-        result.readline = some(ReadLineResult(
-          prompt: "Filename: ",
-          value: path
-        ))
-      of INPUT_CHECKBOX:
-        input.checked = not input.checked
-        input.invalid = true
+proc click(buffer: Buffer, clickable: Element): ClickResult =
+  case clickable.tagType
+  of TAG_LABEL:
+    let label = HTMLLabelElement(clickable)
+    let control = label.control
+    if control != nil:
+      return buffer.click(control)
+  of TAG_SELECT:
+    result.repaint = buffer.setFocus(clickable)
+  of TAG_A:
+    result.repaint = buffer.restoreFocus()
+    let url = parseURL(HTMLAnchorElement(clickable).href, clickable.document.baseURL.some)
+    if url.issome:
+      result.open = some(newRequest(url.get, HTTP_GET))
+  of TAG_OPTION:
+    let option = HTMLOptionElement(clickable)
+    let select = option.select
+    if select != nil:
+      if buffer.document.focus == select:
+        # select option
+        if not select.attrb("multiple"):
+          for option in select.options:
+            option.selected = false
+        option.selected = true
+        result.repaint = buffer.restoreFocus()
+      else:
+        # focus on select
+        result.repaint = buffer.setFocus(select)
+  of TAG_BUTTON:
+    let button = HTMLButtonElement(clickable)
+    if button.form != nil:
+      case button.ctype
+      of BUTTON_SUBMIT: result.open = submitForm(button.form, button)
+      of BUTTON_RESET:
+        button.form.reset()
         result.repaint = true
         buffer.do_reshape()
-      of INPUT_RADIO:
-        for radio in input.radiogroup:
-          radio.checked = false
-          radio.invalid = true
-        input.checked = true
-        input.invalid = true
+      of BUTTON_BUTTON: discard
+  of TAG_TEXTAREA:
+    result.repaint = buffer.setFocus(clickable)
+    let textarea = HTMLTextAreaElement(clickable)
+    result.readline = some(ReadLineResult(
+      value: textarea.value,
+      area: true
+    ))
+  of TAG_INPUT:
+    result.repaint = buffer.restoreFocus()
+    let input = HTMLInputElement(clickable)
+    case input.inputType
+    of INPUT_SEARCH:
+      result.repaint = buffer.setFocus(input)
+      result.readline = some(ReadLineResult(
+        prompt: "SEARCH: ",
+        value: input.value
+      ))
+    of INPUT_TEXT, INPUT_PASSWORD:
+      result.repaint = buffer.setFocus(input)
+      result.readline = some(ReadLineResult(
+        prompt: "TEXT: ",
+        value: input.value,
+        hide: input.inputType == INPUT_PASSWORD
+      ))
+    of INPUT_FILE:
+      result.repaint = buffer.setFocus(input)
+      var path = if input.file.issome:
+        input.file.get.path.serialize_unicode()
+      else:
+        ""
+      result.readline = some(ReadLineResult(
+        prompt: "Filename: ",
+        value: path
+      ))
+    of INPUT_CHECKBOX:
+      input.checked = not input.checked
+      input.invalid = true
+      result.repaint = true
+      buffer.do_reshape()
+    of INPUT_RADIO:
+      for radio in input.radiogroup:
+        radio.checked = false
+        radio.invalid = true
+      input.checked = true
+      input.invalid = true
+      result.repaint = true
+      buffer.do_reshape()
+    of INPUT_RESET:
+      if input.form != nil:
+        input.form.reset()
         result.repaint = true
         buffer.do_reshape()
-      of INPUT_RESET:
-        if input.form != nil:
-          input.form.reset()
-          result.repaint = true
-          buffer.do_reshape()
-      of INPUT_SUBMIT, INPUT_BUTTON:
-        if input.form != nil:
-          result.open = submitForm(input.form, input)
-      else:
-        result.repaint = buffer.restoreFocus()
+    of INPUT_SUBMIT, INPUT_BUTTON:
+      if input.form != nil:
+        result.open = submitForm(input.form, input)
     else:
       result.repaint = buffer.restoreFocus()
+  else:
+    result.repaint = buffer.restoreFocus()
+
+proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} =
+  if buffer.lines.len <= cursory: return
+  let clickable = buffer.getCursorClickable(cursorx, cursory)
+  if clickable != nil:
+    return buffer.click(clickable)
 
 proc readCanceled*(buffer: Buffer): bool {.proxy.} =
   return buffer.restoreFocus()
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 90ec690e..d6820d50 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -303,6 +303,8 @@ type
     form* {.jsget.}: HTMLFormElement
     value* {.jsget.}: string
 
+  HTMLLabelElement* = ref object of HTMLElement
+
 # Forward declarations
 func attrb*(element: Element, s: string): bool
 proc attr*(element: Element, name, value: string)
@@ -404,6 +406,11 @@ iterator elements*(node: Node, tag: TagType): Element {.inline.} =
     if desc.tagType == tag:
       yield desc
 
+iterator elements*(node: Node, tag: set[TagType]): Element {.inline.} =
+  for desc in node.elements:
+    if desc.tagType in tag:
+      yield desc
+
 iterator inputs(form: HTMLFormElement): HTMLInputElement {.inline.} =
   for control in form.controls:
     if control.tagType == TAG_INPUT:
@@ -1136,7 +1143,8 @@ func formmethod*(element: Element): FormMethod =
 
   return FORM_METHOD_GET
 
-func target*(element: Element): string {.jsfunc.} =
+#TODO ??
+func target0*(element: Element): string =
   if element.attrb("target"):
     return element.attr("target")
   for base in element.document.elements(TAG_BASE):
@@ -1144,6 +1152,45 @@ func target*(element: Element): string {.jsfunc.} =
       return base.attr("target")
   return ""
 
+# <base>
+func target(base: HTMLBaseElement): string {.jsfget.} =
+  base.attr("target")
+
+proc target(base: HTMLBaseElement, target: string) {.jsfset.} =
+  base.attr("target", target)
+
+# <anchor>
+func target(anchor: HTMLAnchorElement): string {.jsfget.} =
+  anchor.attr("target")
+
+proc target(anchor: HTMLAnchorElement, target: string) {.jsfset.} =
+  anchor.attr("target", target)
+
+# <label>
+func htmlFor(label: HTMLLabelElement): string {.jsfget.} =
+  label.attr("for")
+
+proc htmlFor(label: HTMLLabelElement, htmlFor: string) {.jsfset.} =
+  label.attr("for", htmlFor)
+
+func control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} =
+  let f = label.htmlFor
+  if f != "":
+    let elem = label.document.getElementById(f)
+    #TODO the supported check shouldn't be needed, just labelable
+    if elem.tagType in SupportedFormAssociatedElements and elem.tagType in LabelableElements:
+      return FormAssociatedElement(elem)
+    return nil
+  for elem in label.elements(LabelableElements):
+    if elem.tagType in SupportedFormAssociatedElements: #TODO remove this
+      return FormAssociatedElement(elem)
+    return nil
+
+func form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} =
+  let control = label.control
+  if control != nil:
+    return control.form
+
 func newText(document: Document, data: string): Text =
   return Text(
     nodeType: TEXT_NODE,
@@ -1236,6 +1283,8 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace
     result = new(HTMLButtonElement)
   of TAG_TEXTAREA:
     result = new(HTMLTextAreaElement)
+  of TAG_LABEL:
+    result = new(HTMLLabelElement)
   else:
     result = new(HTMLElement)
   result.nodeType = ELEMENT_NODE
@@ -2144,5 +2193,8 @@ proc addDOMModule*(ctx: JSContext) =
   ctx.registerType(HTMLTemplateElement, parent = htmlElementCID)
   ctx.registerType(HTMLUnknownElement, parent = htmlElementCID)
   ctx.registerType(HTMLScriptElement, parent = htmlElementCID)
+  ctx.registerType(HTMLBaseElement, parent = htmlElementCID)
+  ctx.registerType(HTMLAreaElement, parent = htmlElementCID)
   ctx.registerType(HTMLButtonElement, parent = htmlElementCID)
   ctx.registerType(HTMLTextAreaElement, parent = htmlElementCID)
+  ctx.registerType(HTMLLabelElement, parent = htmlElementCID)