brunob / leaflet.fullscreen

Leaflet.Control.FullScreen for Leaflet
https://brunob.github.io/leaflet.fullscreen/
MIT License
376 stars 107 forks source link

ES6 module build issue #123

Open pmev0 opened 2 months ago

pmev0 commented 2 months ago

For anyone having problems during build with es6 modules, for example using vite, you can modify the code to make it work. Import leaflet in the .js file and replace the require function call inside the else if statement with the imported leaflet L object (line 13 of original .js file):

import * as L from 'leaflet' . . . module.exports = factory(L);

see full code below.

pmev0 commented 2 months ago
import * as L from 'leaflet'

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // define an AMD module that requires 'leaflet'
        // and resolve to an object containing leaflet
        define('leafletFullScreen', ['leaflet'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // define a CommonJS module that requires 'leaflet'
        module.exports = factory(L);
    } else {
        // Assume 'leaflet' are loaded into global variable already
        factory(root.L);
    }
}(typeof self !== 'undefined'
    ? self
    : this, (leaflet) => {
    'use strict';

    if (typeof document === 'undefined') {
        console.warn('"window.document" is undefined; leaflet.fullscreen requires this object to access the DOM');
        return false;
    }

    const nativeAPI = (() => {
        const methodMap = [
            // Standard
            [
                'requestFullscreen',
                'exitFullscreen',
                'fullscreenElement',
                'fullscreenEnabled',
                'fullscreenchange',
                'fullscreenerror'
            ],
            // New WebKit
            [
                'webkitRequestFullscreen',
                'webkitExitFullscreen',
                'webkitFullscreenElement',
                'webkitFullscreenEnabled',
                'webkitfullscreenchange',
                'webkitfullscreenerror'
            ]
        ];

        const baseList = methodMap[0];
        const ret = {};

        for (const methodList of methodMap) {
            if (methodList[1] in document) {
                for (let i = 0; i < methodList.length; i++) {
                    ret[baseList[i]] = methodList[i];
                }
                return ret;
            }
        }

        return false;
    })();

    const eventNameMap = {
        change: nativeAPI.fullscreenchange,
        error: nativeAPI.fullscreenerror,
    };

    const fullscreenAPI = {
        request(element, options) {
            return new Promise((resolve, reject) => {
                const onFullScreenEntered = function () {
                    this.off('change', onFullScreenEntered);
                    resolve();
                }.bind(this);

                this.on('change', onFullScreenEntered);
                element = element || document.documentElement;
                const returnPromise = element[nativeAPI.requestFullscreen](options);
                if (returnPromise instanceof Promise) {
                    returnPromise.then(onFullScreenEntered).catch(reject);
                }
            });
        },
        exit() {
            return new Promise((resolve, reject) => {
                if (!this.isFullscreen) {
                    resolve();
                    return;
                }

                const onFullScreenExit = function () {
                    this.off('change', onFullScreenExit);
                    resolve();
                }.bind(this);

                this.on('change', onFullScreenExit);
                const returnPromise = document[nativeAPI.exitFullscreen]();
                if (returnPromise instanceof Promise) {
                    returnPromise.then(onFullScreenExit).catch(reject);
                }
            });
        },
        on(event, callback) {
            const eventName = eventNameMap[event];
            if (eventName) {
                document.addEventListener(eventName, callback, false);
            }
        },
        off(event, callback) {
            const eventName = eventNameMap[event];
            if (eventName) {
                document.removeEventListener(eventName, callback, false);
            }
        },
        nativeAPI: nativeAPI
    };

    Object.defineProperties(fullscreenAPI, {
        isFullscreen: {
            get() {
                return Boolean(document[nativeAPI.fullscreenElement]);
            }
        },
        isEnabled: {
            enumerable: true,
            get() {
                // Coerce to boolean in case of old WebKit
                return Boolean(document[nativeAPI.fullscreenEnabled]);
            }
        }
    });

    leaflet.Control.FullScreen = leaflet.Control.extend({
        options: {
            position: 'topleft',
            title: 'Full Screen',
            titleCancel: 'Exit Full Screen',
            forceSeparateButton: false,
            forcePseudoFullscreen: false,
            fullscreenElement: false
        },

        _screenfull: fullscreenAPI,

        onAdd(map) {
            let className = 'leaflet-control-zoom-fullscreen';
            let container;
            let content = '';

            if (map.zoomControl && !this.options.forceSeparateButton) {
                container = map.zoomControl._container;
            } else {
                container = leaflet.DomUtil.create('div', 'leaflet-bar');
            }

            if (this.options.content) {
                content = this.options.content;
            } else {
                className += ' fullscreen-icon';
            }

            this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
            this._map.fullscreenControl = this;

            this._map.on('enterFullscreen exitFullscreen', this._toggleState, this);

            return container;
        },

        onRemove() {
            leaflet.DomEvent
                .off(this.link, 'click', leaflet.DomEvent.stop)
                .off(this.link, 'click', this.toggleFullScreen, this);

            if (this._screenfull.isEnabled) {
                leaflet.DomEvent
                    .off(this._container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .off(this._container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);

                leaflet.DomEvent
                    .off(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .off(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);
            }
        },

        _createButton(title, className, content, container, fn, context) {
            this.link = leaflet.DomUtil.create('a', className, container);
            this.link.href = '#';
            this.link.title = title;
            this.link.innerHTML = content;

            this.link.setAttribute('role', 'button');
            this.link.setAttribute('aria-label', title);

            L.DomEvent.disableClickPropagation(container);

            leaflet.DomEvent
                .on(this.link, 'click', leaflet.DomEvent.stop)
                .on(this.link, 'click', fn, context);

            if (this._screenfull.isEnabled) {
                leaflet.DomEvent
                    .on(container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .on(container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);

                leaflet.DomEvent
                    .on(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .on(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);
            }

            return this.link;
        },

        toggleFullScreen() {
            const map = this._map;
            map._exitFired = false;
            if (map._isFullscreen) {
                if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                    this._screenfull.exit().then(() => map.invalidateSize());
                } else {
                    leaflet.DomUtil.removeClass(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container, 'leaflet-pseudo-fullscreen');
                    map.invalidateSize();
                }
                map.fire('exitFullscreen');
                map._exitFired = true;
                map._isFullscreen = false;
            } else {
                if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                    this._screenfull.request(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container).then(() => map.invalidateSize());
                } else {
                    leaflet.DomUtil.addClass(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container, 'leaflet-pseudo-fullscreen');
                    map.invalidateSize();
                }
                map.fire('enterFullscreen');
                map._isFullscreen = true;
            }
        },

        _toggleState() {
            this.link.title = this._map._isFullscreen
                ? this.options.title
                : this.options.titleCancel;
            this._map._isFullscreen
                ? L.DomUtil.removeClass(this.link, 'leaflet-fullscreen-on')
                : L.DomUtil.addClass(this.link, 'leaflet-fullscreen-on');
        },

        _handleFullscreenChange(ev) {
            const map = this._map;
            if (ev.target === map.getContainer() && !this._screenfull.isFullscreen && !map._exitFired) {
                this._screenfull.exit().then(() => map.invalidateSize());
                map.fire('exitFullscreen');
                map._exitFired = true;
                map._isFullscreen = false;
            }
        }
    });

    leaflet.Map.include({
        toggleFullscreen() {
            this.fullscreenControl.toggleFullScreen();
        }
    });

    leaflet.Map.addInitHook(function () {
        if (this.options.fullscreenControl) {
            this.addControl(leaflet.control.fullscreen(this.options.fullscreenControlOptions));
        }
    });

    leaflet.control.fullscreen = function (options) {
        return new leaflet.Control.FullScreen(options);
    };

    return { leaflet };
}));
brunob commented 2 months ago

Any thoughts on this @BePo65 ?

BePo65 commented 2 months ago

This hack solves the problem of using the plugin in an esm environment (as 'fullscreen' has a dependency - leaflet - we have a 'require' in the code, which will break an esm app).

I will have to do a short investigation to find out, if there is a more general solution. Will take a few days.

pmev0 commented 2 months ago

This hack solves the problem of using the plugin in an esm environment (as 'fullscreen' has a dependency - leaflet - we have a 'require' in the code, which will break an esm app).

I will have to do a short investigation to find out, if there is a more general solution. Will take a few days.

Thanks. A more general solution would be awesome. Here's more information to help you. I am using react with vite and am importing this module along with the css in another file like so:

import './LeafletFullscreen';
import './LeafletFullscreen.css';

My hack works for building, but breaks in development (npm run dev) with vite's HMR. Console error:

Uncaught TypeError: __vite__cjsImport0_leaflet is not a function
    at LeafletFullscreen.js:1:100

The injected module by vite has the following contents:

import __vite__cjsImport0_leaflet from "/node_modules/.vite/deps/leaflet.js?v=c58dd4fd"; const L = __vite__cjsImport0_leaflet

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // define an AMD module that requires 'leaflet'
        // and resolve to an object containing leaflet
        define('leafletFullScreen', ['leaflet'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // define a CommonJS module that requires 'leaflet'
        module.exports = factory(L);
    } else {
        // Assume 'leaflet' are loaded into global variable already
        factory(root.L);
    }
}(typeof self !== 'undefined'
    ? self
    : this, (leaflet) => {
    'use strict';

    if (typeof document === 'undefined') {
        console.warn('"window.document" is undefined; leaflet.fullscreen requires this object to access the DOM');
        return false;
    }

    const nativeAPI = (() => {
        const methodMap = [
            // Standard
            [
                'requestFullscreen',
                'exitFullscreen',
                'fullscreenElement',
                'fullscreenEnabled',
                'fullscreenchange',
                'fullscreenerror'
            ],
            // New WebKit
            [
                'webkitRequestFullscreen',
                'webkitExitFullscreen',
                'webkitFullscreenElement',
                'webkitFullscreenEnabled',
                'webkitfullscreenchange',
                'webkitfullscreenerror'
            ]
        ];

        const baseList = methodMap[0];
        const ret = {};

        for (const methodList of methodMap) {
            if (methodList[1] in document) {
                for (let i = 0; i < methodList.length; i++) {
                    ret[baseList[i]] = methodList[i];
                }
                return ret;
            }
        }

        return false;
    })();

    const eventNameMap = {
        change: nativeAPI.fullscreenchange,
        error: nativeAPI.fullscreenerror,
    };

    const fullscreenAPI = {
        request(element, options) {
            return new Promise((resolve, reject) => {
                const onFullScreenEntered = function () {
                    this.off('change', onFullScreenEntered);
                    resolve();
                }.bind(this);

                this.on('change', onFullScreenEntered);
                element = element || document.documentElement;
                const returnPromise = element[nativeAPI.requestFullscreen](options);
                if (returnPromise instanceof Promise) {
                    returnPromise.then(onFullScreenEntered).catch(reject);
                }
            });
        },
        exit() {
            return new Promise((resolve, reject) => {
                if (!this.isFullscreen) {
                    resolve();
                    return;
                }

                const onFullScreenExit = function () {
                    this.off('change', onFullScreenExit);
                    resolve();
                }.bind(this);

                this.on('change', onFullScreenExit);
                const returnPromise = document[nativeAPI.exitFullscreen]();
                if (returnPromise instanceof Promise) {
                    returnPromise.then(onFullScreenExit).catch(reject);
                }
            });
        },
        on(event, callback) {
            const eventName = eventNameMap[event];
            if (eventName) {
                document.addEventListener(eventName, callback, false);
            }
        },
        off(event, callback) {
            const eventName = eventNameMap[event];
            if (eventName) {
                document.removeEventListener(eventName, callback, false);
            }
        },
        nativeAPI: nativeAPI
    };

    Object.defineProperties(fullscreenAPI, {
        isFullscreen: {
            get() {
                return Boolean(document[nativeAPI.fullscreenElement]);
            }
        },
        isEnabled: {
            enumerable: true,
            get() {
                // Coerce to boolean in case of old WebKit
                return Boolean(document[nativeAPI.fullscreenEnabled]);
            }
        }
    });

    leaflet.Control.FullScreen = leaflet.Control.extend({
        options: {
            position: 'topleft',
            title: 'Full Screen',
            titleCancel: 'Exit Full Screen',
            forceSeparateButton: false,
            forcePseudoFullscreen: false,
            fullscreenElement: false
        },

        _screenfull: fullscreenAPI,

        onAdd(map) {
            let className = 'leaflet-control-zoom-fullscreen';
            let container;
            let content = '';

            if (map.zoomControl && !this.options.forceSeparateButton) {
                container = map.zoomControl._container;
            } else {
                container = leaflet.DomUtil.create('div', 'leaflet-bar');
            }

            if (this.options.content) {
                content = this.options.content;
            } else {
                className += ' fullscreen-icon';
            }

            this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
            this._map.fullscreenControl = this;

            this._map.on('enterFullscreen exitFullscreen', this._toggleState, this);

            return container;
        },

        onRemove() {
            leaflet.DomEvent
                .off(this.link, 'click', leaflet.DomEvent.stop)
                .off(this.link, 'click', this.toggleFullScreen, this);

            if (this._screenfull.isEnabled) {
                leaflet.DomEvent
                    .off(this._container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .off(this._container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);

                leaflet.DomEvent
                    .off(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .off(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);
            }
        },

        _createButton(title, className, content, container, fn, context) {
            this.link = leaflet.DomUtil.create('a', className, container);
            this.link.href = '#';
            this.link.title = title;
            this.link.innerHTML = content;

            this.link.setAttribute('role', 'button');
            this.link.setAttribute('aria-label', title);

            L.DomEvent.disableClickPropagation(container);

            leaflet.DomEvent
                .on(this.link, 'click', leaflet.DomEvent.stop)
                .on(this.link, 'click', fn, context);

            if (this._screenfull.isEnabled) {
                leaflet.DomEvent
                    .on(container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .on(container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);

                leaflet.DomEvent
                    .on(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                    .on(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);
            }

            return this.link;
        },

        toggleFullScreen() {
            const map = this._map;
            map._exitFired = false;
            if (map._isFullscreen) {
                if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                    this._screenfull.exit().then(() => map.invalidateSize());
                } else {
                    leaflet.DomUtil.removeClass(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container, 'leaflet-pseudo-fullscreen');
                    map.invalidateSize();
                }
                map.fire('exitFullscreen');
                map._exitFired = true;
                map._isFullscreen = false;
            } else {
                if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                    this._screenfull.request(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container).then(() => map.invalidateSize());
                } else {
                    leaflet.DomUtil.addClass(this.options.fullscreenElement
                        ? this.options.fullscreenElement
                        : map._container, 'leaflet-pseudo-fullscreen');
                    map.invalidateSize();
                }
                map.fire('enterFullscreen');
                map._isFullscreen = true;
            }
        },

        _toggleState() {
            this.link.title = this._map._isFullscreen
                ? this.options.title
                : this.options.titleCancel;
            this._map._isFullscreen
                ? L.DomUtil.removeClass(this.link, 'leaflet-fullscreen-on')
                : L.DomUtil.addClass(this.link, 'leaflet-fullscreen-on');
        },

        _handleFullscreenChange(ev) {
            const map = this._map;
            if (ev.target === map.getContainer() && !this._screenfull.isFullscreen && !map._exitFired) {
                this._screenfull.exit().then(() => map.invalidateSize());
                map.fire('exitFullscreen');
                map._exitFired = true;
                map._isFullscreen = false;
            }
        }
    });

    leaflet.Map.include({
        toggleFullscreen() {
            this.fullscreenControl.toggleFullScreen();
        }
    });

    leaflet.Map.addInitHook(function () {
        if (this.options.fullscreenControl) {
            this.addControl(leaflet.control.fullscreen(this.options.fullscreenControlOptions));
        }
    });

    leaflet.control.fullscreen = function (options) {
        return new leaflet.Control.FullScreen(options);
    };

    return { leaflet };
}));

Notice the prefix: cjs_import. I tried tweaking the tsconfig.json, tsconfig.node.json and vite.config.ts settings to make it work but without any luck. Another plugin I use is this https://github.com/unbam/Leaflet.SlideMenu/blob/master/src/L.Control.SlideMenu.js and it works without any issues, both when building but also when in dev mode. Maybe their approach can help. Thanks in advance.

BePo65 commented 2 months ago

@pmev0 i didn't want to show any kind of disrespect by using 'hack' 😄

The plugin of unbam works, because he simply extends the global leaflet object. But this plugin here creates an umd module that is more flexible (i.e. can be used in more different environments). Our problem here is the dependency from the leaflet package that adds the require('leaflet') - the culprit of the error.

So I will try to find a solution that works in esm too (when I do remember it correctly, I use this plugin in an angular environment with 'import' - I have to look, how I did it there, but at the moment I'm heavily busy with another project; so be patient please).

pmev0 commented 2 months ago

@pmev0 i didn't want to show any kind of disrespect by using 'hack' 😄

The plugin of unbam works, because he simply extends the global leaflet object. But this plugin here creates an umd module that is more flexible (i.e. can be used in more different environments). Our problem here is the dependency from the leaflet package that adds the require('leaflet') - the culprit of the error.

So I will try to find a solution that works in esm too (when I do remember it correctly, I use this plugin in an angular environment with 'import' - I have to look, how I did it there, but at the moment I'm heavily busy with another project; so be patient please).

No offense/disrespect taken :), it is a hack afterall. I like this plugin and hope for a more general solution, let me know if you need me to test in my environment once you come up with a solution.

pmev0 commented 2 months ago

i tested for another 15 minutes or so, the below solution seems to work, both in dev mode and also build process(in my esm environment with react+vite). In another file, where leaflet is imported as L, I then import addLeafletFullscreen below it( i was getting errors that leaflet has not being initialized) and execute it with no arguments. Not sure why this approach works over the iife import one (maybe HMR breaks the import order in dev mode?), but it solves the issue in my environment. Feel free to share a more general solution, if you find the time.

import * as L from 'leaflet';

export const addLeafletFullscreen = () => {
    (function (root, factory) {
        if (typeof define === 'function' && define.amd) {
            // define an AMD module that requires 'leaflet'
            // and resolve to an object containing leaflet
            define('leafletFullScreen', ['leaflet'], factory);
        } else if (typeof module === 'object' && module.exports) {
            // define a CommonJS module that requires 'leaflet'
            module.exports = factory(L);
        } else {
            // Assume 'leaflet' are loaded into global variable already
            factory(root.L);
        }
    }(typeof self !== 'undefined'
        ? self
        : this, (leaflet) => {
            'use strict';

            if (typeof document === 'undefined') {
                console.warn('"window.document" is undefined; leaflet.fullscreen requires this object to access the DOM');
                return false;
            }

            const nativeAPI = (() => {
                const methodMap = [
                    // Standard
                    [
                        'requestFullscreen',
                        'exitFullscreen',
                        'fullscreenElement',
                        'fullscreenEnabled',
                        'fullscreenchange',
                        'fullscreenerror'
                    ],
                    // New WebKit
                    [
                        'webkitRequestFullscreen',
                        'webkitExitFullscreen',
                        'webkitFullscreenElement',
                        'webkitFullscreenEnabled',
                        'webkitfullscreenchange',
                        'webkitfullscreenerror'
                    ]
                ];

                const baseList = methodMap[0];
                const ret = {};

                for (const methodList of methodMap) {
                    if (methodList[1] in document) {
                        for (let i = 0; i < methodList.length; i++) {
                            ret[baseList[i]] = methodList[i];
                        }
                        return ret;
                    }
                }

                return false;
            })();

            const eventNameMap = {
                change: nativeAPI.fullscreenchange,
                error: nativeAPI.fullscreenerror,
            };

            const fullscreenAPI = {
                request(element, options) {
                    return new Promise((resolve, reject) => {
                        const onFullScreenEntered = function () {
                            this.off('change', onFullScreenEntered);
                            resolve();
                        }.bind(this);

                        this.on('change', onFullScreenEntered);
                        element = element || document.documentElement;
                        const returnPromise = element[nativeAPI.requestFullscreen](options);
                        if (returnPromise instanceof Promise) {
                            returnPromise.then(onFullScreenEntered).catch(reject);
                        }
                    });
                },
                exit() {
                    return new Promise((resolve, reject) => {
                        if (!this.isFullscreen) {
                            resolve();
                            return;
                        }

                        const onFullScreenExit = function () {
                            this.off('change', onFullScreenExit);
                            resolve();
                        }.bind(this);

                        this.on('change', onFullScreenExit);
                        const returnPromise = document[nativeAPI.exitFullscreen]();
                        if (returnPromise instanceof Promise) {
                            returnPromise.then(onFullScreenExit).catch(reject);
                        }
                    });
                },
                on(event, callback) {
                    const eventName = eventNameMap[event];
                    if (eventName) {
                        document.addEventListener(eventName, callback, false);
                    }
                },
                off(event, callback) {
                    const eventName = eventNameMap[event];
                    if (eventName) {
                        document.removeEventListener(eventName, callback, false);
                    }
                },
                nativeAPI: nativeAPI
            };

            Object.defineProperties(fullscreenAPI, {
                isFullscreen: {
                    get() {
                        return Boolean(document[nativeAPI.fullscreenElement]);
                    }
                },
                isEnabled: {
                    enumerable: true,
                    get() {
                        // Coerce to boolean in case of old WebKit
                        return Boolean(document[nativeAPI.fullscreenEnabled]);
                    }
                }
            });

            leaflet.Control.FullScreen = leaflet.Control.extend({
                options: {
                    position: 'topleft',
                    title: 'Full Screen',
                    titleCancel: 'Exit Full Screen',
                    forceSeparateButton: false,
                    forcePseudoFullscreen: false,
                    fullscreenElement: false
                },

                _screenfull: fullscreenAPI,

                onAdd(map) {
                    let className = 'leaflet-control-zoom-fullscreen';
                    let container;
                    let content = '';

                    if (map.zoomControl && !this.options.forceSeparateButton) {
                        container = map.zoomControl._container;
                    } else {
                        container = leaflet.DomUtil.create('div', 'leaflet-bar');
                    }

                    if (this.options.content) {
                        content = this.options.content;
                    } else {
                        className += ' fullscreen-icon';
                    }

                    this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
                    this._map.fullscreenControl = this;

                    this._map.on('enterFullscreen exitFullscreen', this._toggleState, this);

                    return container;
                },

                onRemove() {
                    leaflet.DomEvent
                        .off(this.link, 'click', leaflet.DomEvent.stop)
                        .off(this.link, 'click', this.toggleFullScreen, this);

                    if (this._screenfull.isEnabled) {
                        leaflet.DomEvent
                            .off(this._container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                            .off(this._container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);

                        leaflet.DomEvent
                            .off(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                            .off(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, this);
                    }
                },

                _createButton(title, className, content, container, fn, context) {
                    this.link = leaflet.DomUtil.create('a', className, container);
                    this.link.href = '#';
                    this.link.title = title;
                    this.link.innerHTML = content;

                    this.link.setAttribute('role', 'button');
                    this.link.setAttribute('aria-label', title);

                    L.DomEvent.disableClickPropagation(container);

                    leaflet.DomEvent
                        .on(this.link, 'click', leaflet.DomEvent.stop)
                        .on(this.link, 'click', fn, context);

                    if (this._screenfull.isEnabled) {
                        leaflet.DomEvent
                            .on(container, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                            .on(container, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);

                        leaflet.DomEvent
                            .on(document, this._screenfull.nativeAPI.fullscreenchange, leaflet.DomEvent.stop)
                            .on(document, this._screenfull.nativeAPI.fullscreenchange, this._handleFullscreenChange, context);
                    }

                    return this.link;
                },

                toggleFullScreen() {
                    const map = this._map;
                    map._exitFired = false;
                    if (map._isFullscreen) {
                        if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                            this._screenfull.exit().then(() => map.invalidateSize());
                        } else {
                            leaflet.DomUtil.removeClass(this.options.fullscreenElement
                                ? this.options.fullscreenElement
                                : map._container, 'leaflet-pseudo-fullscreen');
                            map.invalidateSize();
                        }
                        map.fire('exitFullscreen');
                        map._exitFired = true;
                        map._isFullscreen = false;
                    } else {
                        if (this._screenfull.isEnabled && !this.options.forcePseudoFullscreen) {
                            this._screenfull.request(this.options.fullscreenElement
                                ? this.options.fullscreenElement
                                : map._container).then(() => map.invalidateSize());
                        } else {
                            leaflet.DomUtil.addClass(this.options.fullscreenElement
                                ? this.options.fullscreenElement
                                : map._container, 'leaflet-pseudo-fullscreen');
                            map.invalidateSize();
                        }
                        map.fire('enterFullscreen');
                        map._isFullscreen = true;
                    }
                },

                _toggleState() {
                    this.link.title = this._map._isFullscreen
                        ? this.options.title
                        : this.options.titleCancel;
                    this._map._isFullscreen
                        ? L.DomUtil.removeClass(this.link, 'leaflet-fullscreen-on')
                        : L.DomUtil.addClass(this.link, 'leaflet-fullscreen-on');
                },

                _handleFullscreenChange(ev) {
                    const map = this._map;
                    if (ev.target === map.getContainer() && !this._screenfull.isFullscreen && !map._exitFired) {
                        this._screenfull.exit().then(() => map.invalidateSize());
                        map.fire('exitFullscreen');
                        map._exitFired = true;
                        map._isFullscreen = false;
                    }
                }
            });

            leaflet.Map.include({
                toggleFullscreen() {
                    this.fullscreenControl.toggleFullScreen();
                }
            });

            leaflet.Map.addInitHook(function () {
                if (this.options.fullscreenControl) {
                    this.addControl(leaflet.control.fullscreen(this.options.fullscreenControlOptions));
                }
            });

            leaflet.control.fullscreen = function (options) {
                return new leaflet.Control.FullScreen(options);
            };

            return { leaflet };
        }));
};
BePo65 commented 2 months ago

Sorry, but we cannot change this project by replacing 'require' with 'import' as this would make it unusable for commonjs users.

But I spent a little time to try to reproduce your problem. I created an example for React+Vite with typescript on stackblitz (at least I hope it looks like such a configuration, as I do not use react on a regular basis). You would have to use the 'open preview in new tab' button on the top right position to see the map.

In this example, the error does not show. Perhaps we can use this example to demonstrate (and hopefully solve) your topic.

pmev0 commented 2 months ago

Sorry, but we cannot change this project by replacing 'require' with 'import' as this would make it unusable for commonjs users.

But I spent a little time to try to reproduce your problem. I created an example for React+Vite with typescript on stackblitz (at least I hope it looks like such a configuration, as I do not use react on a regular basis). You would have to use the 'open preview in new tab' button on the top right position to see the map.

In this example, the error does not show. Perhaps we can use this example to demonstrate (and hopefully solve) your topic.

Hi. I never suggested to replace require with import. I suggested a more general solution, since the current state of this project did not work for me out of the box. However your stackblitz example does indeed work in dev mode and by turning off strict in tsconfig.app.json i could also bundle it and run it with npm run preview. I will compare the compilersettings, specifically the target es2020 etc. Those sometimes cause problems. Thanks again and i will report back.

BePo65 commented 2 months ago

As I already said, I am not an experienced React programmer, but I use typescript in Angular projects on a daily basis. So when I inspect the stackblitz demo, I see that it has "strict": true, in 'tsconfig.app.json' and in ''tsconfig.node.json'.

So now I'm curious what you find out about the differences between the demo and your real world app.

brunob commented 2 months ago

Thx again @BePo65 ! I hope you are not tired about all these kind of request specific to react of other frameworks. Feel free to tell me when you want we close them. Maybe we should add a statement in readme in order to tell that we can not debug all theses specific cases and only take care that the script work on basic environment. Any thought about it ?

BePo65 commented 2 months ago

@brunob I do not have any problems with such questions - I use it to learn something about other topics / projects; perhaps I can use it anytime in the future for my projects. So IMHO a statement in the readme is not required

pmev0 commented 2 months ago

good news. Initially, for whatever reason( i thought it is not viable to use this plugin if installed with npm), I downloaded the .js and .css files and manually placed them into my project and then imported those in another module (relative import) where I initialized the map (see post https://github.com/brunob/leaflet.fullscreen/issues/123#issuecomment-2168371766) . So then I went ahead and compared the bundle created on stackblitz with the one generated on my end locally. Mine sems to keep the require statement and does not properly transform the module. I then compared with stackblitz, installed the plugin with npm too and the files are now in the node_modules folder, which makes life for the bundler easier i guess. And that was it. It all checks out now, works like a charm. Sorry for making this complicated. May I suggest updating the readme and adding an installation step for those who want to use it with npm? Something like "Installation with npm:

  1. npm install leaflet.fullscreen
  2. import javascript and css in your module:
    import 'leaflet.fullscreen/Control.FullScreen.css';
    import 'leaflet.fullscreen';
BePo65 commented 2 months ago

AFAIK using the plugin after installing it with e.g. npm makes this plugin a module and that is the 'secret". A module must (I think it is 'must') have a package.json that identifies the type of module - in our case it is 'commonjs' and then the rust bundler knows how to handle it.

If you only add the js file to your project then it will become part of your project and that is a 'esm' project (see the 'type' property in your package.json that says 'module').

@brunob so here we are again at the point, where it might be advisable to give some hints about using the plugin in different environments - e.g. plain html, react, angular or so). What do you think about that?

brunob commented 2 months ago

@brunob so here we are again at the point, where it might be advisable to give some hints about using the plugin in different environments - e.g. plain html, react, angular or so). What do you think about that?

Why not, but i'm wondering if other leaflet plugins does that. Is it our job to tell js devs how to use their tools & frameworks ? Anyway, if you have motivation on this, feel free to do it, personally i can't since i don't use any of these frameworks ^^