How to play sound effects in Vue 3 app with Composition API

I am attempting to setup a basic button click in Vue 3 Composition API to trigger a sound effect. Currently, my setup function includes an mp3 sound effect imported from the assets folder then passed into a ref method with an HTMLAudioElement type, then assigned to a const called “play”. After adding the “play” constant to the return, I then added “play” to a click handler in the button. I’m not getting errors in the console, but the button is still not triggering the sound effect. How can I go about configuring the setup function and button to trigger the sound effect? Here is my code:

<template>
  <div div class="flex justify-center">
    <button @click="play">Click</button>
  </div>
</template>

<script>
import { ref } from 'vue' 
import { trumpetSfx } from '../assets/demo_src_assets_fanfare.mp3'

export default {
  name: 'Button',
  components: {},
  setup() {
    const play = ref<HTMLAudioElement>(trumpetSfx);

    return { play }
  }
}
</script>

404 error with POST request using express server

I am running this function that should post data to my express server. The function is called when a button is clicked.

const fetchData = async () => {
    const response = await fetch('http://localhost:1337/api/test', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        message: 'hello world'
      }),
    })

    // const data = await response.json()
    const data = await response
    console.log(data)
  }

Here is my express configuration

const express = require('express')
const cors = require('cors')

const app = express()
app.use(cors())
app.use(express.json())

app.get('/api/test', (req: any, res: any) => {
  console.log(req.body)
  res.json({ status: 'ok' })
})

app.listen(1337, () => {
  console.log('Server started on 1337')
})

The problem is that when I click the button I receive a 404 error for the POST request and my console.log(response) results in the following.

Response { type: "cors", url: "http://localhost:1337/api/test", redirected: false, status: 404, ok: false, statusText: "Not Found", headers: Headers, body: ReadableStream, bodyUsed: false }
​
body: ReadableStream { locked: false }
​
bodyUsed: false
​
headers: Headers {  }
​
ok: false
​
redirected: false
​
status: 404
​
statusText: "Not Found"
​
type: "cors"
​
url: "http://localhost:1337/api/test"
​
<prototype>: ResponsePrototype { clone: clone(), arrayBuffer: arrayBuffer(), blob: blob(), … }

NodeJS return data from for() function

This might be a very basic question but I’ve never done it and I can’t
make it so far, so I’m gonna asking it here!

I have a function in separate js file and it supposed to return messages from for loop data but not sure where the return has to go!

this is my code:

function test(x,y) {
    for(let i in y) {
        if(y[i].value == 'x') {
            return bot.message(y[i]);
        }
        sleep(1500);
    }
}

this doesn’t return anything.

So what I’m trying to do here is to send multiple messages from my loop and put 1.5 seconds sleep between each message.

What did I do wrong?

how to define and register a custom lable in Vue in google chrome extension

I am using vue "vue": "^1.0.24" to develop a google chrome extension right now. This is my widget template.html file:

<st-div id="__selection-translator__">
  <st-div class="__st-box__" v-el:st-box :style="boxStyle">
    <st-header v-if="!inline" v-el:st-drag>
      <st-span class="st-icon-pin" title="固定" @click="pinned=!pinned" :class="{'__pinned__':pinned}"></st-span>
      <st-span class="st-icon-down-open" :class="{'__open__':showForm}" :title="showForm?'收起':'展开'" @click="showForm=!showForm"></st-span>
      <st-span class="st-icon-cog" title="设置" @click="openOptions"></st-span>
    </st-header>
    <st-div class="__query-form__" v-show="showForm">
      <st-div>
        <textarea placeholder="输入要翻译的句子或单词" v-model="query.text" v-el:textarea @keyup.enter="ctrlEnter" @keyup="safeTranslate | debounce 600"></textarea>
      </st-div>
      <st-div>
        <select v-model="query.from" @change="safeTranslate">
          <option value="">自动判断</option>
          <option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
        </select>
        <st-div class="__exchange__">
          <st-span class="st-icon-exchange" @click="exchangeLocale"></st-span>
        </st-div>
        <select v-model="query.to" @change="safeTranslate">
          <option value="">自动选择</option>
          <option v-for="locale in locales" track-by="localeId" :value="locale.localeId" v-text="locale['zh-CN']"></option>
        </select>
      </st-div>
      <st-div>
        <select v-model="query.api" @change="safeTranslate">
          <option value="YouDao">有道翻译</option>
          <option value="BaiDu">百度翻译</option>
          <!-- <option value="Bing">必应翻译</option> -->
          <option value="Google">谷歌翻译</option>
          <option value="GoogleCN">谷歌翻译(国内)</option>
          <option value="Reddwarf">红矮星翻译</option>
        </select>
        <st-div class="__action-list__">
          <st-div class="__button__ __btn-translate__" @click="safeTranslate">翻译
            <st-span class="st-icon-down-dir"></st-span>
          </st-div>
          <st-div class="__expand__">
            <st-div class="__button__" @click="play(query.text,query.from)">朗读</st-div>
            <st-div class="__button__" @click="copy(query.text,$event)">复制</st-div>
          </st-div>
        </st-div>
      </st-div>
    </st-div>
    <st-div class="__translate-result__" v-show="loading">正在查询,请稍候……</st-div>
    <st-div class="__translate-result__" v-show="showResult && !loading">
      <st-div v-show="result.error">
        <st-span v-text="result.error"></st-span>
        <st-span class="__retry__" @click="safeTranslate">重试</st-span>
      </st-div>
      <st-div v-else>
        <st-div class="__phonetic__">
          <st-span v-show="result.phonetic" v-text="result.phonetic"></st-span>
          <st-span class="__copy-and-read__">
            <st-span @click="play(query.text,query.from)">朗读</st-span>
            <st-span v-show="result.phonetic" @click="copy(result.phonetic,$event)">复制</st-span>
            <st-span v-show="result.phonetic" @click="addGlossary(query.text,query.from)">添加到单词本</st-span>
          </st-span>
        </st-div>
        <st-div v-show="result.dict && result.dict.length">
          <st-ul>
            <st-li v-for="d in result.dict" v-text="d"></st-li>
          </st-ul>
          <st-div class="__copy-and-read__">
            <st-span class="__copy-and-read__" @click="copy(result.dict,$event)">复制</st-span>
          </st-div>
        </st-div>
        <st-div v-show="result.result&&result.result.length">
          <st-div v-for="text in result.result" v-text="text"></st-div>
          <st-div class="__copy-and-read__">
            <st-span class="__copy-and-read__" @click="play(result.result,result.to)">朗读</st-span>
            <st-span class="__copy-and-read__" @click="copy(result.result,$event)">复制</st-span>
          </st-div>
        </st-div>
      </st-div>
    </st-div>
    <st-footer>
      <st-span v-show="!loading && apiName">via
        <a :href="result.link" target="_blank" v-text="apiName"></a></st-span>
    </st-footer>
  </st-div>
  <st-div class="__st-btn__" v-el:st-btn :style="btnStyle">译</st-div>
</st-div>

when I run this extension, the console show warning like this:

vue.common.js:1137 [Vue warn]: Unknown custom element: <st-span> - did you register the component correctly? For recursive components, make sure to provide the "name" option.

and this is index.js file:

/**
 * @files 基础 ST 组件,内容脚本和弹出页都会用到
 */

import '../fontello/css/selection-translator.css';
import './style.scss';
import Vue from 'vue';
import widgetMixin from './vue-st';
import chromeCall from 'chrome-call';

import locales from '../locales';
import template from './template.html';
// const request = require('superagent');



// 去掉 locales 里的 *-* 类语种,除了 zh-CN、zh-TW 和 zh-HK(百度翻译里的粤语)
const translateLocales = [];

locales.forEach( locale => {
  const {localeId} = locale;

  if ( !localeId.includes( '-' ) || ( localeId === 'zh-CN' || localeId == 'zh-TW' || localeId == 'zh-HK' ) ) {
    translateLocales.push( locale );
  }
} );

const resolvedEmptyPromise = Promise.resolve() ,
  noop = ()=> {};

/**
 * 翻译窗口的基础 Vue 构造函数。
 * 注意:这个构造函数需要一个额外的 options:client
 */
export default Vue.extend( {
  template ,
  data : ()=>({
    access_token: '', // 扇贝单词授权 token
    locales : translateLocales ,
    showForm : false ,
    query : {
      text : '' ,
      from : '' ,
      to : '' ,
      api : ''
    } ,
    result : {
      error : '' ,
      phonetic : '' ,
      dict : [] ,
      result : [] ,
      link : '',
    }
  }) ,
  created() {
    this.$options.client.on( 'disconnect' , ()=> {
      this.result = {
        error : 'index连接到翻译引擎时发生了错误,请刷新网页或重启浏览器后再试。'
      }
    } );
  } ,
  computed : {
    apiName() {
      return {
        YouDao: '有道翻译',
        Google: '谷歌翻译',
        GoogleCN: '谷歌翻译(国内)',
        BaiDu: '百度翻译',
        Reddwarf: '红矮星翻译'
      }[this.query.api] || ''
    }
  },
  methods : {

    /**
     * 翻译快捷键:Ctrl + Enter
     * @param event
     */
    ctrlEnter( event ) {
      if ( event.ctrlKey ) {
        this.safeTranslate();
      }
    } ,

    /**
     * 仅当有文本时才翻译
     */
    safeTranslate() {
      if ( this.query.text.trim() ) {
        this.translate();
      }
    } ,

    /**
     * 从后台网页获取查询结果
     * @returns {Promise}
     */
    getResult() {
      if ( this.$options.client.disconnected ) {
        return resolvedEmptyPromise;
      }
      return this.$options.client
        .send( 'get translate result' , this.query , true )
        .then( resultObj => {
          debugger;
          if ("200" !==resultObj.response.statusCode||"200" !== resultObj.response.resultCode) {
            let errMsg = {
              NETWORK_ERROR: '网络错误,请检查你的网络设置。',
              API_SERVER_ERROR: '接口返回了错误的数据,请稍候重试。',
              UNSUPPORTED_LANG: '不支持的语种,请使用谷歌翻译重试。',
              NETWORK_TIMEOUT: '查询超时:5 秒内没有查询到翻译结果,已中断查询。'
            }[resultObj.code]
            if (resultObj.error) {
              errMsg += resultObj.error
            }
            this.result = {error: errMsg}
          } else {
            const {phonetic} = resultObj;
            this.result = resultObj;
            this.result.error = '';
            this.result.phonetic = resultObj.response.result.translation;
          }
        } , noop );
      // 只有在一种特殊情况下才会走进 catch 分支:
      // 消息发送出去后但还没得到响应时就被后台断开了连接.
      // 不过出现这种情况的可能性极低.
    } ,

    /**
     * 交换源语种与目标语种
     */
    exchangeLocale() {
      const {to,from} = this.query;
      this.query.to = from;
      this.query.from = to;
    } ,

    /**
     * 打开设置页
     */
    openOptions() {
      this.$options.client.send( 'open options' );
    } ,

    /**
     * 复制文本
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
    copy( textOrTextArray , event ) {
      if ( Array.isArray( textOrTextArray ) ) {
        textOrTextArray = textOrTextArray.join( 'n' );
      }
      this.$options.client.send( 'copy' , textOrTextArray );

      const {target} = event ,
        original = target.textContent;
      target.textContent = '已复制';
      setTimeout( ()=> target.textContent = original , 2000 );
    } ,
    /**
     * 添加单词
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
    addWord(text, event) {
      chromeCall('storage.local.get', ['access_token'])
        .then((res) => {
          if (res.access_token) {
            this.access_token = res.access_token;
            this.queryWord(text, event);
          } else {
            alert('未绑定扇贝账号,请授权绑定')
            this.gotoAccessToken();
          }
        });
    },
    /**
     * 添加单词
     * @param {String|String[]} textOrTextArray
     * @param {MouseEvent} event
     */
     addGlossary(text, event) {
      chromeCall('storage.local.get', ['reddwarf_access_token'])
        .then((res) => {
          if (res.access_token) {
            this.access_token = res.access_token;
            this.queryWord(text, event);
          } else {
            alert('未绑定红矮星账号,请授权绑定')
            this.gotoReddwarfAccessToken();
          }
        });
    },
    gotoAccessToken() {
      chrome.runtime.sendMessage({ action: 'shanbay_authorize' })
    },
    gotoReddwarfAccessToken() {
      chrome.runtime.sendMessage({ action: 'reddwarf_authorize' })
    },
    queryWord(text, event) {
      let params = { word: text, access_token: this.access_token }
      request.get('https://api.shanbay.com/bdc/search/')
        .query(params)
        .end((err, res) => {
          switch (res.status) {
            case 200:
              let info = res.body
              if (info.status_code == 0) {
                this.realAddWord(info.data.id, event);
              } else {
                alert(`查词错误, ${info.msg}`)
              }
              break;
            case 401:
              alert('token 失效,请重新授权')
              this.gotoAccessToken()
              break;
            case 429:
              alert('今日请求次数过多')
              break;
            default:
              alert(`未知错误, ${err}`)
              break;
          }
        })
    },

    realAddWord(id, event) {
      let params = { id: id, access_token: this.access_token }
      request.post('https://api.shanbay.com/bdc/learning/')
        .type('form')
        .send(params)
        .end((err, res) => {
          switch (res.status) {
            case 200:
              let info = res.body
              if (info.status_code == 0) {
                const { target } = event;
                let original = target.textContent;
                target.textContent = '已添加';
                setTimeout(() => target.textContent = original, 2000);
              } else {
                alert(`添加单词发生错误, ${info.msg}`)
              }
              break;
            default:
              alert(`添加单词发生错误, ${err}`)
              break;
          }
        })
    },

    /**
     * 播放语音
     * @param {String|String[]} textOrTextArray
     * @param {String} [lang] - 文本的语种
     */
    play( textOrTextArray , lang ) {
      if ( Array.isArray( textOrTextArray ) ) {
        textOrTextArray = textOrTextArray.join( 'n' );
      }
      this.$options.client.send( 'play' , {
        text : textOrTextArray ,
        api : this.query.api ,
        from : lang
      } );
    }
  } ,
  mixins : [ widgetMixin ]
} );

I read the docs that tell me should register the custom lable like this:

components : {
    'st-span' : <component>
  },

but I did not know how to implement the custom <component>, how to define the <component> of st-span/st-div/st-footer?

How to put div above another div and increase height of background div

I am new to html and css. I have seen this question asked many times but I can’t seems to get it working to fit my problem.

I have 3 div tags. when the user swipe the top div tag I want the the bottom div tag to increase its height and cause the web page to scroll. then move all the div tags align at the top.

The reason I am doing this is; this will cause webpage running on a mobile to go to full screen.

  <div id="backExpander" style="height: 100%; display: inline-block; position: relative;">
  </div>
  <div id="canvasHolder" style="width: 100%; height: 100%;background-color: transparent;">
  <canvas id="GameCanvas" oncontextmenu="event.preventDefault()" tabindex="0"></canvas>
  </div>

  <div id="swipeUpHolder" style="height: 100%; width: 100%; display:block;"  >
  </div>

The script I am using is;

document.getElementById("swipeUpHolder").addEventListener('touchmove', onTouchMove, {passive: false});

function onTouchMove(e)
{
  e.preventDefault

  document.getElementById("backExpander").style.height = "120%";
 
  setTimeout((function() {
                
        document.getElementById("swipeUpHolder").style.display = "none";
        window.scrollTo(0, 0);
          }
        ), 50);
}

As you can see I am increasing the height of background div but it does not do anything.

if I increase height of top div, this will work

document.getElementById("swipeUpHolder").style.height = "120%";

But I want to hide the top div. use the bottom div to push the page to go into scroll.

Can anyone provide some advice on how to fix this?

Where in the ECMAScript specification can I find the reason about why {} !== {}?

In JavaScript We all know that {} !== {} – they are different objects allocated on heap. But I was trying to find the reason at the language specification level.

at https://tc39.es/ecma262/#sec-samevaluenonnumeric it says

  1. If x and y are the same Object value, return true. Otherwise, return false.

But it is unclear to me how the spec defines exactly when two objects are considered to have the same Object value. Like {} and {} clearly do not have the same value but how exactly is it defined in the spec?

How to check multiple selectors to trigger an event?

New to jquery but I am working on a responsive calendar. Now I have a start date and an end date field. If the end date is populated, the reoccurrence check box shows as it removes it from the not-visible class.

How can I add a check on the start date also? I want the check box to show when both of the start and end fields are filled not just one.

 $("#end_date").on("input", function (){
            if($('#end_date').length >= 1) {
                $("#reoccurrence").removeClass('not-visible');
            } else {
                $("#reoccurrence").addClass('not-visible');
            }
        });
.not-visible {
    display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="col">
  <div class="md-form md-form-container-fix">
    <input type="text" id="start_date" class="form-control date-picker" name="start_date" required />
    <label for="start_date">Start Date</label>
  </div>
</div>

<div class="col">
  <div class="md-form md-form-container-fix">
    <input type="text" id="end_date" class="form-control date-picker" name="end_date" required />
    <label for="end_date">End Date</label>
  </div> 
</div>

<div class="form-check not-visible" id="reoccurrence" name="reoccurrence">
  <input class="form-check-input" type="checkbox" id="is_reoccurring" name="is_reoccurring" value="1" />
  <label class="form-check-label" for="is_reoccurring"> Reoccurrence? </label>
</div>

Nested HTML element not assigning to slot

In the below code I am trying to assign <span slot='test-slot'>b</span> to <slot name='test-slot'>a</slot> but the assignment does not work. If I bring <span slot='test-slot'>b</span> outside of its parent <div> container the assignment does take place as expected.

Why is this? Is there anyway you can assign from nested elements with the slot element? If not, any alternatives? This is obviously a reduced test case but in my real web component, it is much more intuitive for a user to add an element with the slot tag within other containers.

<!DOCTYPE html>
<html>
    <head>
    </head>
    <body>
        <test-element>
            <div>
                <span slot='test-slot'>b</span>
            </div>
        </test-element>

        <template id='template-test-element'>
            <slot name='test-slot'>a</slot>
        </template>

        <script>
            class TestElement extends HTMLElement {
                constructor() {
                    super();

                    // Initialise shadow root and attach table template
                    let shadowRoot = this.attachShadow({ mode: "open" });
                    shadowRoot.appendChild(document.getElementById("template-test-element").content.cloneNode(true));
                }
            }

            customElements.define('test-element', TestElement);
        </script>
    </body>
</html>

Tabulator custom formatter breaks cell height

I’ve encountered a weird issue in Tabulator where the cell heights are very small when all columns are using a custom formatter.

See this fiddle: https://jsfiddle.net/LukeAtkinsFNM/soLk5p0x/11/

The two formatters I’m using are a float formatter and a luxon DateTime formatter. They each return a string value.

Tabulator.extendModule("format", "formatters", {

    float: (cell, params, onRendered) => {
        var val = cell.getValue();
        if (val === null || val === undefined || isNaN(Number(val))) return "";
        if (params.showZeros !== undefined) {
            if (!params.showZeros && val == 0) return "-   ";
        }
        var text = Number(val).toLocaleString("en-AU", {
            notation: "standard",
            minimumFractionDigits: params.digits || 2,
            maximumFractionDigits: params.digits || 2,
        });
        if (params.units) text += " " + params.units;
        return text;
    },

    DateTime: (cell, params, onRendered) => {
        var dt = cell.getValue();
        if (dt == null) return "";
        if (typeof(dt) == "number" || !isNaN(dt)) dt = DateTime.fromMillis(Number(dt));
        if (!dt.toFormat) return dt.toString();
        if (!params.format) params.format = "yyyy-MM-dd hh:mm";
        var text = dt.toFormat(params.format);
        return text;
    },

});

Rows with complete data seem fine, but when I add a blank row, the row height is correct, but the cell heights are tiny which causes issue when using the cell editors.

If I add a blank column it seems to fix the issue, but it’s not a desirable solution.
Does anyone have any ideas?

How do I install and make use of heatmap.js?

I downloaded the most recent release from Github but none of it seems to work and to be perfectly honest, I’m not really sure what to do with it from here. I’ve never used a library before and can’t seem to implement it to even begin just playing around with it.

I’ve moved the folder to my project root and linked it in the HTML but nothing is showing for me.

Where do I even begin with things like this? I’ve tried to do lots of reading but I’m getting lost in a sea of various different libraries and package managers.

Thanks

How can I re-load images in p5.js once they’ve changed since runtime?

I have a python file outputting a .png file to a local directory once a second, and this p5.js Javascript file is being hosted from this directory on a server. My issue is in trying to use the loadImage() function to load this local .png — if I do so, it’ll only refer to the initial value of it, and not its new changed value. Even when using a callback function inside draw(), it is very buggy, and does not change. Here’s the file:

let splot;

//...
//irrelevant classes
//...

function draw() {
  background(245);
  solOnePane.display();
  s1plot = loadImage("plot.png", imageLoaded);
}

function imageLoaded()
{
  console.log("Debug");
    image(s1plot,200,200);
}

The result is a super buggy and unchanging image file that only reflects the image file at the state of loading the site. Upon a refresh, the file is updated. So, how can I write code with loadImage() to load an image at the current moment?

Thanks so much.

Filter search for UL

If I search any child keyword it’s working fine but, If I search any parent keyword and if the keyword matches, it should visible with their great grandparent, child and sub child in the hierarchy view. Can we do that.

<input type="text" class="bar_input" placeholder="Search">
    <ul class="myUL">
      <li><span class="caret">Beverages</span>
        <ul class="nested">
          <li>Water</li>
          <li>Coffee</li>
          <li><span class="caret">Tea</span>
            <ul class="nested">
              <li>Black Tea</li>
              <li>White Tea</li>
              <li><span class="caret">Green Tea</span>
                <ul class="nested">
                  <li>Sencha</li>
                  <li>Gyokuro</li>
                  <li>Matcha</li>
                  <li>Pi Lo Chun</li>
                </ul>
              </li>
            </ul>
          </li>  
        </ul>
      </li>
    </ul>

$('.bar_input').on('keyup',function(){
  var input = $('.bar_input').val().toLowerCase();
    var tags = $('.myUL li');
    var count = tags.length;
    $('.caret').addClass('caret-down');   $('.nested').addClass('active');

    for (i = 0; i < count; i++) {
        if (!input || tags[i].textContent.toLowerCase().indexOf(input) > -1) {
            tags[i].style['display'] = 'block';
        } else {
            tags[i].style['display'] = 'none';
        }
    }
})

javascript: All device data is read into the object

When I write javascript, I want to read all the data of the device into the object, but only the last one is read.
I would like to ask how I can read all the data in the device into obj, thank you.

let data = [
      {
          "bind": "82218018295591013",
          "account": "admin",
          "password": "0000",
          "devices": [
              {
                  "ip": "192.168.0.88",
                  "brand_name": "UNIVIEW",
                  "name": "UNIVIEW"
              }
          ]
      },
      {
          "bind": "94907378021478863",
          "account": "admin",
          "password": "0000",
          "devices": [
              {
                  "ip": "192.168.0.88",
                  "brand_name": "UNIVIEW",
                  "name": "UNIVIEW"
              },
              {
                  "ip": "192.168.0.89",
                  "brand_name": "hisharp",
                  "name": "hisharp"
              }
          ]
      }
  ]
  
  let obj = {};
  
  function getObj() {
      for (let i = 0; i < data.length; i++) {
          for (let j = 0; j < data[i].devices.length; j++) {
              obj.devices = data[i].devices[j];
          }
      }

      return obj;
  }

  console.log('obj :>> ', obj);
  getObj();

set GoogleMap widget gesturehandling to “greedy” on flutter

I’m working on a flutter web application and I’m using GoogleMap widget.
There are several interaction modes (gestureHandling) that google makes available in the JavaScript version (Interaction doc).

By default, the map is in “greedy” mode, but when I resize the internet browser, it goes into “cooperative” mode … and at this time, when I want to zoom on my map, I have to maintain CTRL key and i dont’t like it at all.

It show me : “Use ctrl + scroll to zoom the map”.

So in my project I want to set the map in the “greedy” interaction mode, but the GoogleMap widget doesn’t have any parameter for doing it.

I found an alternative solution which consists of going through JavaScript to programmatically press the CTRL button when there is an action of the wheel on the map.

<!-- In web/index.html -->
<!-- ... -->
<head>
  <!-- ... -->
  <script src="script.js" defer></script>
  <!-- ... -->
</head>
<!-- ... -->
// In web/script.js
function initScrollListener(id) {
    try {
        var map = document.getElementById('plugins.flutter.io/google_maps_' + id);
        map.addEventListener('wheel', wheelEvent, true);
    } catch (error) {
        console.error(error);
    }
}

function wheelEvent(event) {
    Object.defineProperty(event, 'ctrlKey', { value: true });
}
// In lib/main.dart (or anywhere in your project)
import 'dart:js' as js;
import 'package:flutter/foundation.dart';

//... in my widget ...//
@override
  Widget build(BuildContext context) {
    return GoogleMap(
      //...//
      onMapCreated: (GoogleMapController controller) {
        _controller.complete(controller);
        if(kIsWeb) js.context.callMethod('initMap', [controller.mapId]);
      },
    );
  }
//...//

It works pretty well, but I’m not too confident about this solution.

I’m now trying to find a way to retrieve the google.maps.Map instance, and attempt to manualy set the “greedy” interaction mode onto it.

If you have any suggest ^^
Thanks !