about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-07-30 21:11:52 +0200
committerbptato <nincsnevem662@gmail.com>2022-07-31 09:27:05 +0200
commit8bc1392c463fbbfd99ae368b9e99dfcb97c52149 (patch)
treeb6f9b49944b58204299f24f49ae25150db003322 /src
parenta26dae6c0123e291352fc7d24699eb8c58193718 (diff)
downloadchawan-8bc1392c463fbbfd99ae368b9e99dfcb97c52149.tar.gz
Add interactive <select>
Diffstat (limited to 'src')
-rw-r--r--src/css/select.nim3
-rw-r--r--src/css/selectorparser.nim4
-rw-r--r--src/css/stylednode.nim8
-rw-r--r--src/html/dom.nim19
-rw-r--r--src/io/buffer.nim35
5 files changed, 62 insertions, 7 deletions
diff --git a/src/css/select.nim b/src/css/select.nim
index f47df9af..0d142558 100644
--- a/src/css/select.nim
+++ b/src/css/select.nim
@@ -47,6 +47,9 @@ func pseudoSelectorMatches[T: Element|StyledNode](elem: T, sel: Selector, felem:
     elif elem.tagType == TAG_OPTION:
       return HTMLOptionElement(elem).selected
     return false
+  of PSEUDO_FOCUS:
+    when selem is StyledNode: felem.addDependency(selem, DEPEND_FOCUS)
+    return elem.document.focus == elem
 
 func selectorsMatch*[T: Element|StyledNode](elem: T, selectors: SelectorList, felem: T = nil): bool
 
diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim
index 012d6e68..846aa865 100644
--- a/src/css/selectorparser.nim
+++ b/src/css/selectorparser.nim
@@ -21,7 +21,7 @@ type
 
   PseudoClass* = enum
     PSEUDO_FIRST_CHILD, PSEUDO_LAST_CHILD, PSEUDO_ONLY_CHILD, PSEUDO_HOVER,
-    PSEUDO_ROOT, PSEUDO_NTH_CHILD, PSEUDO_CHECKED
+    PSEUDO_ROOT, PSEUDO_NTH_CHILD, PSEUDO_CHECKED, PSEUDO_FOCUS
 
   CombinatorType* = enum
     DESCENDANT_COMBINATOR, CHILD_COMBINATOR, NEXT_SIBLING_COMBINATOR,
@@ -212,6 +212,8 @@ proc parseSelectorToken(state: var SelectorParser, csstoken: CSSToken) =
         state.addSelector(Selector(t: PSEUDO_SELECTOR, pseudo: PSEUDO_ROOT))
       of "checked":
         state.addSelector(Selector(t: PSEUDO_SELECTOR, pseudo: PSEUDO_CHECKED))
+      of "focus":
+        state.addSelector(Selector(t: PSEUDO_SELECTOR, pseudo: PSEUDO_FOCUS))
     of QUERY_PSELEM:
       case csstoken.value
       of "before":
diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim
index 52a13771..f75e3f66 100644
--- a/src/css/stylednode.nim
+++ b/src/css/stylednode.nim
@@ -39,7 +39,7 @@ type
     STYLED_ELEMENT, STYLED_TEXT
 
   DependencyType* = enum
-    DEPEND_HOVER, DEPEND_CHECKED
+    DEPEND_HOVER, DEPEND_CHECKED, DEPEND_FOCUS
 
   DependencyInfo* = object
     # All nodes we depend on, for each dependency type d.
@@ -91,12 +91,18 @@ func isValid*(styledNode: StyledNode): bool =
       of DEPEND_CHECKED:
         if child.depends.prev[d] != elem.checked:
           return false
+      of DEPEND_FOCUS:
+        let focus = elem.document.focus == elem
+        if child.depends.prev[d] != focus:
+          return false
   return true
 
 proc applyDependValues*(styledNode: StyledNode) =
   let elem = Element(styledNode.node)
   styledNode.depends.prev[DEPEND_HOVER] = elem.hover
   styledNode.depends.prev[DEPEND_CHECKED] = elem.checked
+  let focus = elem.document.focus == elem
+  styledNode.depends.prev[DEPEND_FOCUS] = focus
   elem.invalid = false
 
 proc addDependency*(styledNode, dep: StyledNode, t: DependencyType) =
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 5dde41fa..e404f18b 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -56,6 +56,7 @@ type
 
     parser_cannot_change_the_mode_flag*: bool
     is_iframe_srcdoc*: bool
+    focus*: Element
 
   CharacterData* = ref object of Node
     data*: string
@@ -110,6 +111,8 @@ type
 
   HTMLSpanElement* = ref object of HTMLElement
 
+  HTMLOptGroupElement* = ref object of HTMLElement
+
   HTMLOptionElement* = ref object of HTMLElement
     selected*: bool
   
@@ -251,7 +254,10 @@ iterator options*(select: HTMLSelectElement): HTMLOptionElement {.inline.} =
   for child in select.children:
     if child.tagType == TAG_OPTION:
       yield HTMLOptionElement(child)
-    #TODO optgroups
+    elif child.tagType == TAG_OPTGROUP:
+      for opt in child.children:
+        if opt.tagType == TAG_OPTION:
+          yield HTMLOptionElement(child)
 
 func qualifiedName*(element: Element): string =
   if element.namespacePrefix.issome: element.namespacePrefix.get & ':' & element.localName
@@ -277,6 +283,12 @@ func body*(document: Document): HTMLElement =
         return HTMLElement(element)
   return nil
 
+func select*(option: HTMLOptionElement): HTMLSelectElement =
+  for anc in option.ancestors:
+    if anc.tagType == TAG_SELECT:
+      return HTMLSelectElement(anc)
+  return nil
+
 func countChildren(node: Node, nodeType: NodeType): int =
   for child in node.childNodes:
     if child.nodeType == nodeType:
@@ -546,6 +558,8 @@ func newHTMLElement*(document: Document, tagType: TagType, namespace = Namespace
   of TAG_SELECT:
     result = new(HTMLSelectElement)
     HTMLSelectElement(result).size = 1
+  of TAG_OPTGROUP:
+    result = new(HTMLOptGroupElement)
   of TAG_OPTION:
     result = new(HTMLOptionElement)
   of TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6:
@@ -672,7 +686,8 @@ func title*(document: Document): string =
   return ""
 
 func disabled*(option: HTMLOptionElement): bool =
-  #TODO optgroup
+  if option.parentElement.tagType == TAG_OPTGROUP and option.parentElement.attrb("disabled"):
+    return true
   return option.attrb("disabled")
 
 func text*(option: HTMLOptionElement): string =
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index 5652e84c..539e2f30 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -229,7 +229,7 @@ func getLink(node: StyledNode): HTMLAnchorElement =
   #TODO ::before links?
 
 const ClickableElements = {
-  TAG_A, TAG_INPUT
+  TAG_A, TAG_INPUT, TAG_OPTION
 }
 
 func getClickable(styledNode: StyledNode): Element =
@@ -789,6 +789,9 @@ proc updateCursor(buffer: Buffer) =
     buffer.cpos.fromy = 0
     buffer.cpos.cursory = buffer.lastVisibleLine - 1
 
+  if buffer.cursory >= buffer.lines.len:
+    buffer.cpos.cursory = max(0, buffer.lines.len - 1)
+
   if buffer.lines.len == 0:
     buffer.cpos.cursory = 0
 
@@ -1108,10 +1111,36 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[ClickAction]
 proc click*(buffer: Buffer): Option[ClickAction] =
   let clickable = buffer.getCursorClickable()
   if clickable != nil:
+    template set_focus(e: Element) =
+      if buffer.document.focus != e:
+        buffer.document.focus = e
+        buffer.reshape = true
+    template restore_focus =
+      if buffer.document.focus != nil:
+        buffer.document.focus = nil
+        buffer.reshape = true
     case clickable.tagType
+    of TAG_SELECT:
+      set_focus clickable
     of TAG_A:
+      restore_focus
       return ClickAction(url: HTMLAnchorElement(clickable).href, httpmethod: HttpGet).some
+    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
+          restore_focus
+        else:
+          # focus on select
+          set_focus select
     of TAG_INPUT:
+      restore_focus
       let input = HTMLInputElement(clickable)
       case input.inputType
       of INPUT_SEARCH:
@@ -1170,9 +1199,9 @@ proc click*(buffer: Buffer): Option[ClickAction] =
           let submitaction = submitForm(input.form, input)
           return submitaction
       else:
-        discard
+        restore_focus
     else:
-      discard
+      restore_focus
 
 proc drawBuffer*(buffer: Buffer) =
   var format = newFormat()