# Source: src/button-send.js

    /** A zero dependency button web component that sends a msg or a document event when clicked.
     *  Contains relevant data from data-*, topic and payload attributes (or properties),
     *  includes a _meta object showing whether any modifier keys were used, the element id/name.
     *
     * See ./docs/button-send.md for detailed documentation on installation and use.
     *
     * Version: See the class code
     *
     **/
    /** Copyright (c) 2022-2024 Julian Knight (Totally Information)
     * https://it.knightnet.org.uk, https://github.com/TotallyInformation
     *
     * Licensed under the Apache License, Version 2.0 (the 'License');
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     * http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an 'AS IS' BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     **/

    /** ToDo:
     * - Add variants (see simple-card)
     * - Allow colour and background colour to be controlled with standard names (e.g. info, warning, error, etc)
     * - Create a unique identifier to use when id/name not specified.
     * - Allow std pre-formatted msg from uibuilder to change the attribs/props
     * - Allow std pre-formatted msg from uibuilder to add a new button
     * - Consider moving from `click` to `pointerdown`/`up`. taking note of `setPointerCapture` to detect when up is outside of the down
     *    target so it can be ignored if desired.
     * - Also add processing for multi-click (detail property of click), contextMenu, auxclick, dblclick
     */

    import TiBaseComponent from '../libs/ti-base-component'

    /** Only use a template if you want to isolate the code and CSS */
    const template = document.createElement('template')
    template.innerHTML = /*html*/`
        <style>
            button, input[type="button" i] {
                margin-top: .5em;
                margin-bottom: .5em;
                display: inline-flex;
                align-items: center;
                justify-content: center;
                border: none;
                padding: .5rem 1rem;
                text-decoration: none;
                background-color: var(--info);
                color: var(--text2);
                font-family: inherit;
                font-size: 1rem;
                line-height: 1.4;
                cursor: pointer;
                text-align: center;
                transition: background 250ms ease-in-out, transform 150ms ease;
                -webkit-appearance: none;
                -moz-appearance: none;
                border-radius: 8px;
                /* box-shadow: 0 3px 5px rgb(var(--uib-color-fg), 0.5); */
                box-shadow: inset 2px 2px 3px rgba(255,255,255, .3),
                            inset -2px -2px 3px rgba(0,0,0, .3);
            }
            button:hover, input[type="button" i]:hover {
                background-color: rgb(var(--uib-color-info), .5);
            }

            button:focus, input[type="button" i]:focus {
                outline: 1px solid rgb(var(--uib-color-fg));
                outline-offset: -4px;
            }

            button:active, input[type="button" i]:active {
                transform: scale(0.97);
            }
        </style>
        <button type="button">
            <span><slot></slot></span>
        </button>
    `

    /** Namespace
     * @namespace Beta
     */

    /** A Zero dependency button web component that sends a msg or a document event when clicked.
     *  Contains relevant data from data-*, topic and payload attributes (or properties),
     *  includes a _meta object showing whether any modifier keys were used, the element id/name
     *
     * @class
     * @augments TiBaseComponent
     * @description Define a new zero dependency custom web component ECMA module that can be used as an HTML tag
     *
     * @element button-send
     * @memberOf Beta

     * METHODS FROM BASE:
     * @method config Update runtime configuration, return complete config
     * @method createShadowSelectors Creates the jQuery-like $ and $$ methods
     * @method deepAssign Object deep merger
     * @method doInheritStyles If requested, add link to an external style sheet
     * @method ensureId Adds a unique ID to the tag if no ID defined.
     * @method _uibMsgHandler Not yet in use
     * @method _event(name,data) Standardised custom event dispatcher

     * OTHER METHODS:
     * None

     * @fires button-send:connected - When an instance of the component is attached to the DOM. `evt.details` contains the details of the element.
     * @fires button-send:ready - Alias for connected. The instance can handle property & attribute changes
     * @fires button-send:disconnected - When an instance of the component is removed from the DOM. `evt.details` contains the details of the element.
     * @fires button-send:attribChanged - When a watched attribute changes. `evt.details` contains the details of the change.
     * @fires button-send:click - Document object event. evt.details contains the data
     * @fires uibuilder.send {function} - Sends a msg back to Node-RED if uibuilder available. topic, payload and _meta props may all be set.
     * NOTE that listeners can be attached either to the `document` or to the specific element instance.

     * Standard watched attributes (common across all my components):
     * @property {string|boolean} inherit-style - Optional. Load external styles into component (only useful if using template). If present but empty, will default to './index.css'. Optionally give a URL to load.
     * @property {string} name - Optional. HTML name attribute. Included in output _meta prop.

     * Other watched attributes:
     * @property {string} topic - Optional. Topic string to use. Mostly for node-red messages
     * @property {string} payload - Optional. Payload string. Mostly for node-red messages. For non-string payload, see props below

     * Standard props (common across all my components):
     * @property {boolean} uib True if UIBUILDER for Node-RED is loaded. In base class
     * @property {function(string): Element} $ jQuery-like shadow dom selector. In base class
     * @property {function(string): NodeList} $$  jQuery-like shadow dom multi-selector. In base class
     * @property {number} _iCount The component version string (date updated). In base class
     * @property {object} opts This components controllable options - get/set using the `config()` method. In base class
     *
     * @property {string} version Static. The component version string (date updated). Also has a getter that returns component and base version strings.

     * Other props:
     * @property {any|string} payload - Can be an attribute or property. If used as property, must not use payload attribute in html, aAllows any data to be attached to payload. As an attribute, allows a string only.
     * By default, all attributes are also created as properties

     * @slot default - Button label. Allows text, inline and most block tags to be included (unlike the standard button tag which only allows inline tags).

     * @csspart button - Uses the uib-styles.css uibuilder master for variables where available.

     * See https://github.com/runem/web-component-analyzer?tab=readme-ov-file#-how-to-document-your-components-using-jsdoc
     */
    class ButtonSend extends TiBaseComponent {
        /** Component version */
        static componentVersion = '2024-10-06'

        sendEvents = true
        /** The topic to include in the output
         * @type {string|undefined} */
        topic
        /** The payload to include in the output
         * @type {any} */
        payload
        /** Standard _ui object to include in msgs */
        _ui = {
            type: 'button-send',
            event: undefined,
            id: undefined,
            name: undefined,
            // data: undefined, // All of the data-* attributes as an object
        }

        /** The output msg @type {object} */
        _msg = {}

        /** Makes HTML attribute change watched
         * @returns {Array<string>} List of all of the html attribs (props) listened to
         */
        static get observedAttributes() {
            return [
                // Standard watched attributes:
                'inherit-style', 'name',
                // Other watched attributes:
                'topic', 'payload',
            ]
        }

        /** NB: Attributes not available here - use connectedCallback to reference */
        constructor() {
            super()
            // Only attach the shadow dom if code and style isolation is needed - comment out if shadow dom not required
            this._construct(template.content.cloneNode(true))

            // const mydata = { ...this.dataset }

            /** The output msg @type {object} */
            this._setMsg('component load')

            // if ( this.uib && this.sendEvents ) window.uibuilder.send({_ui: {...this._ui}})
        } // --- end of constructor --- //

        /** Runs when an instance is added to the DOM */
        connectedCallback() {
            this._connect() // Keep at start.

            /** Listen for the button click */
            this.addEventListener('click', this.handleClick)

            /** Instance registration event @type {CustomEvent} */
            // this._setMsg('instance load')
            // document.dispatchEvent( new CustomEvent('button-send:instanceAdded', {'detail': this._msg._ui}) )
            // if ( window.uibuilder && this.sendEvents ) window.uibuilder.send({_ui: {...this._ui}})

            this._ready() // Keep at end. Let everyone know that a new instance of the component has been connected & is ready
        }

        /** Runs when an instance is removed from the DOM */
        disconnectedCallback() {
            this.removeEventListener('click', this.handleClick)

            this._disconnect() // Keep at end.
        }

        /** Runs when an observed attribute changes - Note: values are always strings
         * @param {string} attrib Name of watched attribute that has changed
         * @param {string} oldVal The previous attribute value
         * @param {string} newVal The new attribute value
         */
        attributeChangedCallback(attrib, oldVal, newVal) {
            /** Optionally ignore attrib changes until instance is fully connected
             * Otherwise this can fire BEFORE everthing is fully connected.
             */
            // if (!this.connected) return

            // Don't bother if the new value same as old
            if ( oldVal === newVal ) return
            // Create a property from the value - WARN: Be careful with name clashes
            this[attrib] = newVal

            // Add other dynamic attribute processing here.
            // If attribute processing doesn't need to be dynamic, process in connectedCallback as that happens earlier in the lifecycle

            this._setMsg('attribute change')

            if ( window['uibuilder'] && this.sendEvents ) { window['uibuilder'].send( {
                payload: { name: attrib, oldVal: oldVal, newVal: newVal },
                _ui: { ...this._ui }
            } ) }

            // Keep at end. Let everyone know that an attribute has changed for this instance of the component
            this._event('attribChanged', { attribute: attrib, newVal: newVal, oldVal: oldVal })
        }

        _setMsg(evtName) {
            const mydata = { ...this.dataset }
            this._msg.topic = this.topic
            this._msg.payload = this.payload ? this.payload : mydata
            this._msg._ui = { ...this._ui }
            if (evtName) this._msg._ui.event = evtName
            if ( this.id !== '') this._msg._ui.id = this.id
            const n = this.getAttribute('name')
            if ( n !== null ) this._msg._ui.name = n
            // this._msg._ui.data = mydata // All of the data-* attributes as an object
        }

        /** fn to run when the button is clicked
         * @param {PointerEvent} evt The event object
         */
        handleClick(evt) {
            const uibLib = window?.['uibuilder']

            evt.preventDefault()
            this._setMsg('click')

            const _ui = this._msg._ui
            const target = /** @type {Element} */ (evt.currentTarget)

            // Get target properties - only shows custom props not element default ones
            const props = {}
            const ignoreProps = ['name', 'sendEvents', 'payload', '$', '_ui', '_msg']
            Object.keys(target).forEach( key => {
                if ( !ignoreProps.includes(key) ) props[key] = target[key]
            })

            const ignoreAttribs = ['class', 'id', 'name']
            const attribs = Object.assign({},
                ...Array.from(target.attributes,
                    ( { name, value } ) => {
                        if ( !ignoreAttribs.includes(name) ) {
                            return ({ [name]: value })
                        }
                        return undefined
                    }
                )
            )

            _ui.slotText = target.textContent !== '' ? target.textContent?.substring(0, 255) : undefined

            _ui.props = props
            _ui.attribs = attribs
            // @ts-ignore
            _ui.dataset = {...target.dataset}
            _ui.classes = Array.from(target.classList)

            _ui.altKey = evt.altKey
            _ui.ctrlKey = evt.ctrlKey
            _ui.shiftKey = evt.shiftKey
            _ui.metaKey = evt.metaKey

            _ui.pointerType = evt.pointerType
            _ui.nodeName = target.nodeName
            if ( uibLib ) {
                _ui.clientId = uibLib.clientId
                _ui.pageName = uibLib.pageName
                _ui.tabId = uibLib.tabId
            }

            /** Output a custom document event `button-send:click`, data is in evt.details */
            document.dispatchEvent( new CustomEvent(`${this.localName}:click`, {
                bubbles: true,
                composed: true,
                'detail': this._msg
            }) )

            /** Send a message to uibuilder with the output data */
            if (uibLib) uibLib.send(this._msg)
            // if (uibLib) uibLib.eventSend(evt)
            // else console.debug('[ButtonSend:handleClick] uibuilder not available, cannot send')
        }
    } // ---- End of class definition ---- //

    // Make the class the default export so it can be used elsewhere
    export default ButtonSend

    /** Self register the class to global
     * Enables new data lists to be dynamically added via JS
     * and lets the static methods be called
     */
    window['ButtonSend'] = ButtonSend

    // Add the class as a new Custom Element to the window object
    customElements.define('button-send', ButtonSend)