def METHOD_NAME() -> None:
    @text_demo('Specifying event attributes', '''
        **A list of strings** names the attributes of the JavaScript event object:
            ```py
            ui.button().on('click', handle_click, ['clientX', 'clientY'])
            ```
        **An empty list** requests _no_ attributes:
            ```py
            ui.button().on('click', handle_click, [])
            ```
        **The value `None`** represents _all_ attributes (the default):
            ```py
            ui.button().on('click', handle_click, None)
            ```
        **If the event is called with multiple arguments** like QTable's "row-click" `(evt, row, index) => void`,
            you can define a list of argument definitions:
            ```py
            ui.table(...).on('rowClick', handle_click, [[], ['name'], None])
            ```
            In this example the "row-click" event will omit all arguments of the first `evt` argument,
            send only the "name" attribute of the `row` argument and send the full `index`.
        If the retrieved list of event arguments has length 1, the argument is automatically unpacked.
        So you can write
        ```py
        ui.button().on('click', lambda e: print(e.args['clientX'], flush=True))
        ```
        instead of
        ```py
        ui.button().on('click', lambda e: print(e.args[0]['clientX'], flush=True))
        ```
        Note that by default all JSON-serializable attributes of all arguments are sent.
        This is to simplify registering for new events and discovering their attributes.
        If bandwidth is an issue, the arguments should be limited to what is actually needed on the server.
    ''')
    def event_attributes() -> None:
        columns = [
            {'name': 'name', 'label': 'Name', 'field': 'name'},
            {'name': 'age', 'label': 'Age', 'field': 'age'},
        ]
        rows = [
            {'name': 'Alice', 'age': 42},
            {'name': 'Bob', 'age': 23},
        ]
        ui.table(columns, rows, 'name').on('rowClick', ui.notify, [[], ['name'], None])
    @text_demo('Modifiers', '''
        You can also include [key modifiers](https://vuejs.org/guide/essentials/event-handling.html#key-modifiers>) (shown in input "A"),
        modifier combinations (shown in input "B"),
        and [event modifiers](https://vuejs.org/guide/essentials/event-handling.html#mouse-button-modifiers>) (shown in input "C").
    ''')
    def modifiers() -> None:
        with ui.row():
            ui.input('A').classes('w-12').on('keydown.space', lambda: ui.notify('You pressed space.'))
            ui.input('B').classes('w-12').on('keydown.y.shift', lambda: ui.notify('You pressed Shift+Y'))
            ui.input('C').classes('w-12').on('keydown.once', lambda: ui.notify('You started typing.'))
    @text_demo('Custom events', '''
        It is fairly easy to emit custom events from JavaScript which can be listened to with `element.on(...)`.
        This can be useful if you want to call Python code when something happens in JavaScript.
        In this example we are listening to the `visibilitychange` event of the browser tab.
    ''')
    async def custom_events() -> None:
        tabwatch = ui.checkbox('Watch browser tab re-entering') \
            .on('tabvisible', lambda: ui.notify('Welcome back!') if tabwatch.value else None, args=[])
        ui.add_head_html(f'''
            <script>
            document.addEventListener('visibilitychange', () => {{
                if (document.visibilityState === 'visible')
                    getElement({tabwatch.id}).$emit('tabvisible');
            }});
            </script>
        ''')
        # END OF DEMO
        await globals.get_client().connected()
        await ui.run_javascript(f'''
            document.addEventListener('visibilitychange', () => {{
                if (document.visibilityState === 'visible')
                    getElement({tabwatch.id}).$emit('tabvisible');
            }});
        ''', respond=False)