diff --git a/examples/js_dom_draw/draw.js.v b/examples/js_dom_draw/draw.js.v
index efa88a1a1b..7add029c06 100644
--- a/examples/js_dom_draw/draw.js.v
+++ b/examples/js_dom_draw/draw.js.v
@@ -1,82 +1,90 @@
-/*
import jsdom
-fn get_2dcontext(canvas jsdom.IElement) ?jsdom.CanvasRenderingContext2D {
- if canvas is jsdom.HTMLCanvasElement {
- c := canvas.get_context('2d')
- match c {
- jsdom.CanvasRenderingContext2D {
- return c
- }
- else {
- return error('cannot fetch 2d context')
- }
+fn get_canvas(elem JS.HTMLElement) &JS.HTMLCanvasElement {
+ match elem {
+ JS.HTMLCanvasElement {
+ return elem
+ }
+ else {
+ panic('Not a canvas')
}
- } else {
- return error('canvas is not an HTMLCanvasElement')
}
}
-fn draw_line(context jsdom.CanvasRenderingContext2D, x1 int, y1 int, x2 int, y2 int) {
- context.begin_path()
- context.set_stroke_style('black')
- context.set_line_width(1)
- context.move_to(x1, y1)
- context.line_to(x2, y2)
+fn draw_line(mut context JS.CanvasRenderingContext2D, x1 int, y1 int, x2 int, y2 int) {
+ context.beginPath()
+ context.strokeStyle = 'black'.str
+ context.lineWidth = JS.Number(1)
+ context.moveTo(x1, y1)
+ context.lineTo(x2, y2)
context.stroke()
- context.close_path()
+ context.closePath()
}
struct DrawState {
mut:
+ context JS.CanvasRenderingContext2D
drawing bool
x int
y int
}
fn main() {
+ window := jsdom.window()
document := jsdom.document
-
- elem := document.get_element_by_id('myButton') ?
- elemc := document.get_element_by_id('myCanvas') or { panic('no canvas') }
- canv := jsdom.get_html_canvas_element(elemc) or { panic('expected canvas') }
-
- context := canv.get_context_2d()
- mut state := DrawState{}
- canv.add_event_listener('mousedown', fn [mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
- state.drawing = true
- if event is jsdom.MouseEvent {
- state.x = event.offset_x()
- state.y = event.offset_y()
+ clear_btn := document.getElementById('clearButton'.str) ?
+ canvas_elem := document.getElementById('canvas'.str) ?
+ canvas := get_canvas(canvas_elem)
+ ctx := canvas.getContext('2d'.str, js_undefined()) ?
+ context := match ctx {
+ JS.CanvasRenderingContext2D {
+ ctx
}
- })
+ else {
+ panic('can not get 2d context')
+ }
+ }
+ mut state := DrawState{context, false, 0, 0}
- canv.add_event_listener('mousemove', fn [context, mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
+ canvas.addEventListener('mousedown'.str, fn [mut state] (event JS.Event) {
+ state.drawing = true
+ match event {
+ JS.MouseEvent {
+ state.x = int(event.offsetX)
+ state.y = int(event.offsetY)
+ }
+ else {}
+ }
+ }, JS.EventListenerOptions{})
+ canvas.addEventListener('mousemove'.str, fn [mut state] (event JS.Event) {
if state.drawing {
- if event is jsdom.MouseEvent {
- draw_line(context, state.x, state.y, event.offset_x(), event.offset_y())
- state.x = event.offset_x()
- state.y = event.offset_y()
+ match event {
+ JS.MouseEvent {
+ draw_line(mut state.context, state.x, state.y, int(event.offsetX),
+ int(event.offsetY))
+ state.x = int(event.offsetX)
+ state.y = int(event.offsetY)
+ }
+ else {}
}
}
- })
+ }, JS.EventListenerOptions{})
- jsdom.window.add_event_listener('mouseup', fn [context, mut state] (_ jsdom.IEventTarget, event jsdom.IEvent) {
+ window.addEventListener('mouseup'.str, fn [mut state] (event JS.Event) {
if state.drawing {
- if event is jsdom.MouseEvent {
- draw_line(context, state.x, state.y, event.offset_x(), event.offset_y())
+ match event {
+ JS.MouseEvent {
+ draw_line(mut state.context, state.x, state.y, int(event.offsetX),
+ int(event.offsetY))
+ }
+ else {}
}
state.x = 0
state.y = 0
state.drawing = false
}
- })
- elem.add_event_listener('click', fn [context, canv] (_ jsdom.IEventTarget, _ jsdom.IEvent) {
- context.clear_rect(0, 0, canv.width(), canv.height())
- })
-}
-*/
-
-fn main() {
- panic('jsdom is being refactored; This example will be available soon')
+ }, JS.EventListenerOptions{})
+ clear_btn.addEventListener('click'.str, fn [mut state, canvas] (_ JS.Event) {
+ state.context.clearRect(0, 0, canvas.width, canvas.height)
+ }, JS.EventListenerOptions{})
}
diff --git a/examples/js_dom_draw/index.html b/examples/js_dom_draw/index.html
index 3d60688a34..bbf9e5cc69 100644
--- a/examples/js_dom_draw/index.html
+++ b/examples/js_dom_draw/index.html
@@ -1,7 +1,7 @@
Drawing with mouse events
-
-
+
+
\ No newline at end of file
diff --git a/vlib/builtin/js/jsfns.js.v b/vlib/builtin/js/jsfns.js.v
index 5f9254c910..2b5d973dd3 100644
--- a/vlib/builtin/js/jsfns.js.v
+++ b/vlib/builtin/js/jsfns.js.v
@@ -57,6 +57,34 @@ pub interface JS.Map {
pub interface JS.Any {}
+pub fn js_is_null(x JS.Any) bool {
+ res := false
+ #res.val = x === null
+
+ return res
+}
+
+pub fn js_is_undefined(x JS.Any) bool {
+ res := false
+ #res.val = x === undefined
+
+ return res
+}
+
+pub fn js_null() JS.Any {
+ mut obj := JS.Any{}
+ #obj = null;
+
+ return obj
+}
+
+pub fn js_undefined() JS.Any {
+ mut obj := JS.Any{}
+ #obj = undefined;
+
+ return obj
+}
+
pub interface JS.Array {
JS.Any // map(fn (JS.Any) JS.Any) JS.Array
map(JS.Any) JS.Array
diff --git a/vlib/jsdom/jsdom.js.v b/vlib/jsdom/jsdom.js.v
index 06c2d5483f..452b68efc1 100644
--- a/vlib/jsdom/jsdom.js.v
+++ b/vlib/jsdom/jsdom.js.v
@@ -8,6 +8,13 @@ pub mut:
will_read_frequently bool
}
+pub fn (settings CanvasRenderingContext2DSettings) to_js() JS.Any {
+ mut object := JS.Any{}
+ #object = { alpha: settings.alpha, colorSpace: settings.color_space.str, desynchronized: settings.desynchronized.val, willReadFrequently: settings.will_read_frequently.val };
+
+ return object
+}
+
pub interface JS.DOMMatrix2DInit {
mut:
a JS.Number
@@ -168,9 +175,9 @@ pub struct JS.EventListenerOptions {
}
pub interface JS.EventTarget {
- addEventListener(cb EventCallback, options JS.EventListenerOptions)
+ addEventListener(event JS.String, cb EventCallback, options JS.EventListenerOptions)
dispatchEvent(event JS.Event) JS.Boolean
- removeEventListener(cb EventCallback, options JS.EventListenerOptions)
+ removeEventListener(event JS.String, cb EventCallback, options JS.EventListenerOptions)
}
// Event is an event which takes place in the DOM.
@@ -320,6 +327,7 @@ pub interface JS.Document {
lastModified JS.String
inputEncoding JS.String
implementation JS.DOMImplementation
+ getElementById(id JS.String) ?JS.HTMLElement
mut:
bgColor JS.String
body JS.HTMLElement
@@ -368,6 +376,14 @@ pub interface JS.Element {
scroll(x JS.Number, y JS.Number)
scrollBy(x JS.Number, y JS.Number)
toggleAttribute(qualifiedName JS.String, force JS.Boolean) JS.Boolean
+ getElementsByClassName(className JS.String) JS.HTMLCollection
+ getElementsByTagName(qualifiedName JS.String) JS.HTMLCollection
+ getEelementsByTagNameNS(namespaecURI JS.String, localName JS.String) JS.HTMLCollection
+ hasAttribute(qualifiedName JS.String) JS.Boolean
+ hasAttributeNS(namespace JS.String, localName JS.String) JS.Boolean
+ hasAttributes() JS.Boolean
+ hasPointerCapture(pointerId JS.Number) JS.Boolean
+ matches(selectors JS.String) JS.Boolean
mut:
className JS.String
id JS.String
@@ -383,6 +399,13 @@ pub const (
document = JS.Document{}
)
+pub fn window() JS.Window {
+ mut x := JS.Any(voidptr(0))
+ #x = window;
+
+ return x
+}
+
fn init() {
#jsdom__document = document;
}
@@ -398,3 +421,143 @@ pub fn event_listener(callback fn (JS.EventTarget, JS.Event)) EventCallback {
callback(target, event)
}
}
+
+pub interface JS.HTMLCollection {
+ length JS.Number
+ item(idx JS.Number) ?JS.Any
+ namedItem(name JS.String) ?JS.Any
+}
+
+pub interface JS.HTMLElement {
+ JS.Element
+ accessKeyLabel JS.String
+ offsetHeight JS.Number
+ offsetLeft JS.Number
+ offsetParent JS.Any
+ offsetTop JS.Number
+ offsetWidth JS.Number
+ click()
+mut:
+ accessKey JS.String
+ autocapitalize JS.String
+ dir JS.String
+ draggable JS.Boolean
+ hidden JS.Boolean
+ innerText JS.String
+ lang JS.String
+ outerText JS.String
+ spellcheck JS.Boolean
+ title JS.String
+ translate JS.Boolean
+}
+
+pub fn JS.HTMLElement.prototype.constructor() JS.HTMLElement
+
+pub interface JS.HTMLEmbedElement {
+ getSVGDocument() ?JS.Document
+mut:
+ align JS.String
+ height JS.String
+ src JS.String
+ width JS.String
+}
+
+pub fn html_embed_type(embed JS.HTMLEmbedElement) JS.String {
+ mut str := JS.String{}
+ #str = embed.type
+
+ return str
+}
+
+pub fn JS.HTMLEmbedElement.prototype.constructor() JS.HTMLEmbedElement
+
+pub type CanvasContext = JS.CanvasRenderingContext2D
+ | JS.WebGL2RenderingContext
+ | JS.WebGLRenderingContext
+
+pub interface JS.HTMLCanvasElement {
+ JS.HTMLElement
+ getContext(contextId JS.String, options JS.Any) ?CanvasContext
+mut:
+ height JS.Number
+ width JS.Number
+}
+
+pub type FillStyle = JS.CanvasGradient | JS.CanvasPattern | JS.String
+
+pub interface JS.CanvasRenderingContext2D {
+ canvas JS.HTMLCanvasElement
+ beginPath()
+ clip(path JS.Path2D, fillRule JS.String)
+ fill(path JS.Path2D, fillRule JS.String)
+ isPointInPath(path JS.Path2D, x JS.Number, y JS.Number, fillRule JS.String) JS.Boolean
+ isPointInStroke(path JS.Path2D, x JS.Number, y JS.Number) JS.Boolean
+ stoke(path JS.Path2D)
+ createLinearGradient(x0 JS.Number, y0 JS.Number, x1 JS.Number, y1 JS.Number) JS.CanvasGradient
+ createRadialGradient(x0 JS.Number, y0 JS.Number, r0 JS.Number, x1 JS.Number, y1 JS.Number, r1 JS.Number) JS.CanvasGradient
+ createPattern(image JS.CanvasImageSource, repetition JS.String) ?JS.CanvasPattern
+ arc(x JS.Number, y JS.Number, radius JS.Number, startAngle JS.Number, endAngle JS.Number, counterclockwise JS.Boolean)
+ arcTo(x1 JS.Number, y1 JS.Number, x2 JS.Number, y2 JS.Number, radius JS.Number)
+ bezierCurveTo(cp1x JS.Number, cp1y JS.Number, cp2x JS.Number, cp2y JS.Number, x JS.Number, y JS.Number)
+ closePath()
+ ellipse(x JS.Number, y JS.Number, radiusX JS.Number, radiusY JS.Number, rotation JS.Number, startAngle JS.Number, endAngle JS.Number, counterclockwise JS.Boolean)
+ lineTo(x JS.Number, y JS.Number)
+ moveTo(x JS.Number, y JS.Number)
+ quadraticCurveTo(cpx JS.Number, cpy JS.Number, x JS.Number, y JS.Number)
+ rect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
+ getLineDash() JS.Array
+ setLineDash(segments JS.Array)
+ clearRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
+ fillRect(x JS.Number, y JS.Number, w JS.null, h JS.Number)
+ strokeRect(x JS.Number, y JS.Number, w JS.Number, h JS.Number)
+ getTransformt() JS.DOMMatrix
+ resetTransform()
+ rotate(angle JS.Number)
+ scale(x JS.Number, y JS.Number)
+ setTransform(matrix JS.DOMMatrix)
+ transform(a JS.Number, b JS.Number, c JS.Number, d JS.Number, e JS.Number, f JS.Number)
+ translate(x JS.Number, y JS.Number)
+ drawFocusIfNeeded(path JS.Path2D, element JS.Element)
+ stroke()
+mut:
+ lineCap JS.String
+ lineDashOffset JS.Number
+ lineJoin JS.String
+ lineWidth JS.Number
+ miterLimit JS.Number
+ fillStyle FillStyle
+ strokeStyle FillStyle
+ globalAlpha JS.Number
+ globalCompositeOperation JS.String
+}
+
+pub interface JS.CanvasGradient {
+ addColorStop(offset JS.Number, color JS.String)
+}
+
+pub interface JS.CanvasPattern {
+ setTransform(transform JS.DOMMatrix)
+}
+
+pub interface JS.WebGLRenderingContext {
+ canvas JS.HTMLCanvasElement
+ drawingBufferHeight JS.Number
+ drawingBufferWidth JS.Number
+}
+
+pub interface JS.WebGL2RenderingContext {
+ JS.WebGLRenderingContext
+}
+
+pub interface JS.Window {
+ JS.EventTarget
+ closed JS.Boolean
+ devicePixelRatio JS.Number
+ document JS.Document
+ frameElement JS.Element
+ innerHeight JS.Number
+ innerWidth JS.Number
+ length JS.Number
+}
+
+pub interface JS.Path2D {}
diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v
index 6d6aa2cbf8..c56ac6b663 100644
--- a/vlib/v/ast/table.v
+++ b/vlib/v/ast/table.v
@@ -267,7 +267,12 @@ pub fn (t &Table) is_same_method(f &Fn, func &Fn) string {
for i in 0 .. f.params.len {
// don't check receiver for `.typ`
has_unexpected_type := i > 0 && f.params[i].typ != func.params[i].typ
-
+ // temporary hack for JS ifaces
+ lsym := t.get_type_symbol(f.params[i].typ)
+ rsym := t.get_type_symbol(func.params[i].typ)
+ if lsym.language == .js && rsym.language == .js {
+ return ''
+ }
has_unexpected_mutability := !f.params[i].is_mut && func.params[i].is_mut
if has_unexpected_type || has_unexpected_mutability {
diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v
index 87434bb47a..135d0e3994 100644
--- a/vlib/v/ast/types.v
+++ b/vlib/v/ast/types.v
@@ -1247,6 +1247,34 @@ pub fn (t &TypeSymbol) find_method_with_generic_parent(name string) ?Fn {
return none
}
+// is_js_compatible returns true if type can be converted to JS type and from JS type back to V type
+pub fn (t &TypeSymbol) is_js_compatible() bool {
+ mut table := global_table
+ if t.kind == .void {
+ return true
+ }
+ if t.kind == .function {
+ return true
+ }
+ if t.language == .js || t.name.starts_with('JS.') {
+ return true
+ }
+ match t.info {
+ SumType {
+ for variant in t.info.variants {
+ sym := table.get_final_type_symbol(variant)
+ if !sym.is_js_compatible() {
+ return false
+ }
+ }
+ return true
+ }
+ else {
+ return true
+ }
+ }
+}
+
pub fn (t &TypeSymbol) str_method_info() (bool, bool, int) {
mut has_str_method := false
mut expects_ptr := false
diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v
index f145233c12..8d4e9b9864 100644
--- a/vlib/v/checker/checker.v
+++ b/vlib/v/checker/checker.v
@@ -413,8 +413,10 @@ pub fn (mut c Checker) sum_type_decl(node ast.SumTypeDecl) {
variant.pos)
} else if sym.kind in [.placeholder, .int_literal, .float_literal] {
c.error('unknown type `$sym.name`', variant.pos)
- } else if sym.kind == .interface_ {
+ } else if sym.kind == .interface_ && sym.language != .js {
c.error('sum type cannot hold an interface', variant.pos)
+ } else if sym.kind == .struct_ && sym.language == .js {
+ c.error('sum type cannot hold an JS struct', variant.pos)
}
if sym.name.trim_prefix(sym.mod + '.') == node.name {
c.error('sum type cannot hold itself', variant.pos)
@@ -555,8 +557,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(method.return_type, method.return_type_pos) or { return }
if is_js {
mtyp := c.table.get_type_symbol(method.return_type)
- if (mtyp.language != .js && !method.return_type.is_void())
- && !mtyp.name.starts_with('JS.') {
+ if !mtyp.is_js_compatible() {
c.error('method $method.name returns non JS type', method.pos)
}
}
@@ -567,8 +568,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(param.typ, param.pos) or { return }
if is_js {
ptyp := c.table.get_type_symbol(param.typ)
- if (ptyp.kind != .function && ptyp.language != .js
- && !ptyp.name.starts_with('JS.')) && !(j == method.params.len - 1
+ if !ptyp.is_js_compatible() && !(j == method.params.len - 1
&& method.is_variadic) {
c.error('method `$method.name` accepts non JS type as parameter',
method.pos)
@@ -595,7 +595,7 @@ pub fn (mut c Checker) interface_decl(mut node ast.InterfaceDecl) {
c.ensure_type_exists(field.typ, field.pos) or { return }
if is_js {
tsym := c.table.get_type_symbol(field.typ)
- if tsym.language != .js && !tsym.name.starts_with('JS.') && tsym.kind != .function {
+ if !tsym.is_js_compatible() {
c.error('field `$field.name` uses non JS type', field.pos)
}
}
@@ -5313,6 +5313,7 @@ pub fn (mut c Checker) expr(node ast.Expr) ast.Type {
defer {
c.expr_level--
}
+
// c.expr_level set to 150 so that stack overflow does not occur on windows
if c.expr_level > 150 {
c.error('checker: too many expr levels: $c.expr_level ', node.position())
@@ -6407,6 +6408,7 @@ fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSym
} else {
expr_type = expr_types[0].typ
}
+
c.smartcast(node.cond, node.cond_type, expr_type, mut branch.scope)
}
}
diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v
index 5a8996ab60..c4f76ff46f 100644
--- a/vlib/v/gen/js/fn.v
+++ b/vlib/v/gen/js/fn.v
@@ -620,8 +620,11 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) {
if is_varg {
g.writeln('$arg_name = new array(new array_buffer({arr: $arg_name,len: new int(${arg_name}.length),index_start: new int(0)}));')
} else {
- if arg.typ.is_ptr() || arg.is_mut {
- g.writeln('$arg_name = new \$ref($arg_name)')
+ asym := g.table.get_type_symbol(arg.typ)
+ if asym.kind != .interface_ && asym.language != .js {
+ if arg.typ.is_ptr() || arg.is_mut {
+ g.writeln('$arg_name = new \$ref($arg_name)')
+ }
}
}
}
@@ -743,7 +746,9 @@ fn (mut g JsGen) gen_anon_fn(mut fun ast.AnonFn) {
if is_varg {
g.writeln('$arg_name = new array(new array_buffer({arr: $arg_name,len: new int(${arg_name}.length),index_start: new int(0)}));')
} else {
- if arg.typ.is_ptr() || arg.is_mut {
+ asym := g.table.get_type_symbol(arg.typ)
+
+ if arg.typ.is_ptr() || (arg.is_mut && asym.kind != .interface_ && asym.language != .js) {
g.writeln('$arg_name = new \$ref($arg_name)')
}
}
diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v
index ecb19f200d..89f3558074 100644
--- a/vlib/v/gen/js/js.v
+++ b/vlib/v/gen/js/js.v
@@ -2343,20 +2343,53 @@ fn (mut g JsGen) match_expr_sumtype(node ast.MatchExpr, is_expr bool, cond_var M
} else {
g.write('if (')
}
+ if sym.kind == .sum_type || sym.kind == .interface_ {
+ x := branch.exprs[sumtype_index]
+
+ if x is ast.TypeNode {
+ typ := g.unwrap_generic(x.typ)
+
+ tsym := g.table.get_type_symbol(typ)
+ if tsym.language == .js && (tsym.name == 'JS.Number'
+ || tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
+ g.write('typeof ')
+ }
+ }
+ }
g.match_cond(cond_var)
if sym.kind == .sum_type {
- g.write(' instanceof ')
- g.expr(branch.exprs[sumtype_index])
+ x := branch.exprs[sumtype_index]
+ if x is ast.TypeNode {
+ typ := g.unwrap_generic(x.typ)
+ tsym := g.table.get_type_symbol(typ)
+ if tsym.language == .js && (tsym.name == 'JS.Number'
+ || tsym.name == 'JS.Boolean' || tsym.name == 'JS.String') {
+ g.write(' === "${tsym.name[3..].to_lower()}"')
+ } else {
+ g.write(' instanceof ')
+ g.expr(branch.exprs[sumtype_index])
+ }
+ } else {
+ g.write(' instanceof ')
+ g.expr(branch.exprs[sumtype_index])
+ }
} else if sym.kind == .interface_ {
if !sym.name.starts_with('JS.') {
g.write('.val')
}
- if branch.exprs[sumtype_index] is ast.TypeNode {
- g.write(' instanceof ')
- g.expr(branch.exprs[sumtype_index])
+ x := branch.exprs[sumtype_index]
+ if x is ast.TypeNode {
+ typ := g.unwrap_generic(x.typ)
+ tsym := g.table.get_type_symbol(typ)
+ if tsym.language == .js && (tsym.name == 'Number'
+ || tsym.name == 'Boolean' || tsym.name == 'String') {
+ g.write(' === $tsym.name.to_lower()')
+ } else {
+ g.write(' instanceof ')
+ g.expr(branch.exprs[sumtype_index])
+ }
} else {
g.write(' instanceof ')
-
g.write('None__')
}
}