(function(F/*fossil object*/){
  /**
     A basic API for creating and managing a copy-to-clipboard button.

     Requires: fossil.bootstrap, fossil.dom
  */
  const D = F.dom;

  /**
     Initializes element e as a copy button using the given options
     object.

     The first argument may be a DOM element or a string (CSS selector
     suitable for use with document.querySelector()).

     Options:

     .copyFromElement: DOM element

     .copyFromId: DOM element ID

     One of copyFromElement or copyFromId must be provided, but copyFromId
     may optionally be provided via e.dataset.copyFromId.

     .extractText: optional callback which is triggered when the copy
     button is clicked. It must return the text to copy to the
     clipboard. The default is to extract it from the copy-from
     element, using its [value] member, if it has one, else its
     [innerText]. A client-provided callback may use any data source
     it likes, so long as it's synchronous. If this function returns a
     falsy value then the clipboard is not modified. This function is
     called with the fully expanded/resolved options object as its
     "this" (that's a different instance than the one passed to this
     function!).

     .cssClass: optional CSS class, or list of classes, to apply to e.

     .style: optional object of properties to copy directly into
     e.style.

     .oncopy: an optional callback function which is added as an event
     listener for the 'text-copied' event (see below). There is
     functionally no difference from setting this option or adding a
     'text-copied' event listener to the element, and this option is
     considered to be a convenience form of that. For the sake of
     framework-level consistency, the default value is a callback
     which passes the copy button to fossil.dom.flashOnce().

     Note that this function's own defaultOptions object holds default
     values for some options. Any changes made to that object affect
     any future calls to this function.

     Be aware that clipboard functionality might or might not be
     available in any given environment. If this button appears to
     have no effect, that may be because it is not enabled/available
     in the current platform.

     The copy button emits custom event 'text-copied' after it has
     successfully copied text to the clipboard. The event's "detail"
     member is an object with a "text" property holding the copied
     text. Other properties may be added in the future. The event is
     not fired if copying to the clipboard fails (e.g. is not
     available in the current environment).

     As a special case, the copy button's click handler is suppressed
     (becomes a no-op) for as long as the element has the CSS class
     "disabled". This allows elements which cannot be disabled via
     HTML attributes, e.g. a SPAN, to act as a copy button while still
     providing a way to disable them.

     Returns the copy-initialized element.

     Example:

     const button = fossil.copyButton('#my-copy-button', {
       copyFromId: 'some-other-element-id'
     });
     button.addEventListener('text-copied',function(ev){
       fossil.dom.flashOnce(ev.target);
       console.debug("Copied text:",ev.detail.text);
     });
  */
  F.copyButton = function f(e, opt){
    if('string'===typeof e){
      e = document.querySelector(e);
    }
    opt = F.mergeLastWins(f.defaultOptions, opt);
    if(opt.cssClass){
      D.addClass(e, opt.cssClass);
    }
    var srcId, srcElem;
    if(opt.copyFromElement){
      srcElem = opt.copyFromElement;
    }else if((srcId = opt.copyFromId || e.dataset.copyFromId)){
      srcElem = document.querySelector('#'+srcId);
    }
    const extract = opt.extractText || (
      undefined===srcElem.value ? ()=>srcElem.innerText : ()=>srcElem.value
    );
    D.copyStyle(e, opt.style);
    e.addEventListener(
      'click',
      function(ev){
        ev.preventDefault();
        ev.stopPropagation();
        if(e.classList.contains('disabled')) return;
        const txt = extract.call(opt);
        if(txt && D.copyTextToClipboard(txt)){
          e.dispatchEvent(new CustomEvent('text-copied',{
            detail: {text: txt}
          }));
        }
      },
      false
    );
    if('function' === typeof opt.oncopy){
      e.addEventListener('text-copied', opt.oncopy, false);
    }
    return e;
  };

  F.copyButton.defaultOptions = {
    cssClass: 'copy-button',
    oncopy: D.flashOnce.eventHandler,
    style: {/*properties copied as-is into element.style*/}
  };
  
})(window.fossil);
