UNPKG

@mux/mux-uploader

Version:

An uploader elements to be used with Mux Direct Uploads

5 lines • 69.7 kB
{ "version": 3, "sources": ["../src/constants.ts", "../src/polyfills/index.ts", "../src/mux-uploader.ts", "../src/utils/element-utils.ts", "../src/mux-uploader-drop.ts", "../src/utils/progress.ts", "../src/mux-uploader-progress.ts", "../src/mux-uploader-status.ts", "../src/mux-uploader-retry.ts", "../src/mux-uploader-pause.ts", "../src/mux-uploader-file-select.ts", "../src/layouts/block.ts", "../src/mux-uploader-sr-text.ts", "../src/index.ts"], "sourcesContent": ["export type ProgressTypes = {\n BAR: 'bar';\n RADIAL: 'radial';\n PERCENTAGE: 'percentage';\n};\n\nexport const ProgressTypes: ProgressTypes = {\n BAR: 'bar',\n RADIAL: 'radial',\n PERCENTAGE: 'percentage',\n};\n", "/* eslint @typescript-eslint/no-empty-function: \"off\" */\n\nclass EventTarget {\n addEventListener() {}\n removeEventListener() {}\n dispatchEvent(_event: Event) {\n return true;\n }\n}\n\n// @github/template-parts requires DocumentFragment to be available on globalThis for SSR\nif (typeof DocumentFragment === 'undefined') {\n class DocumentFragment extends EventTarget {}\n // @ts-ignore\n globalThis.DocumentFragment = DocumentFragment;\n}\n\nclass HTMLElement extends EventTarget {}\nclass HTMLVideoElement extends EventTarget {}\n\nconst customElements: CustomElementRegistry = {\n get(_name: string) {\n return undefined;\n },\n define(_name, _constructor, _options) {},\n getName(_constructor) {\n return null;\n },\n upgrade(_root) {},\n whenDefined(_name) {\n return Promise.resolve(HTMLElement as unknown as CustomElementConstructor);\n },\n};\n\nclass CustomEvent {\n #detail;\n get detail() {\n return this.#detail;\n }\n constructor(_typeArg: string, eventInitDict: CustomEventInit = {}) {\n // super(typeArg, eventInitDict);\n this.#detail = eventInitDict?.detail;\n }\n initCustomEvent() {}\n}\n\nfunction createElement(_tagName: string, _options?: ElementCreationOptions): HTMLElement {\n return new HTMLElement();\n}\n\nconst globalThisShim = {\n document: {\n createElement,\n },\n DocumentFragment,\n customElements,\n CustomEvent,\n EventTarget,\n HTMLElement,\n HTMLVideoElement,\n};\n\n// const isServer = typeof window === 'undefined' || typeof globalThis.customElements === 'undefined';\n// const GlobalThis = isServer ? globalThisShim : globalThis;\n// const Document = isServer ? globalThisShim.document : globalThis.document;\n//\n// export { GlobalThis as globalThis, Document as document };\nconst isServer = typeof window === 'undefined' || typeof globalThis.customElements === 'undefined';\ntype GlobalThis = typeof globalThis;\nconst internalGlobalThis: GlobalThis = (isServer ? globalThisShim : globalThis) as GlobalThis;\nconst internalDocument: Document = (isServer ? globalThisShim.document : globalThis.document) as Document;\n\nexport { internalGlobalThis as globalThis, internalDocument as document };\n", "import { globalThis, document } from './polyfills';\n\nimport { UpChunk } from '@mux/upchunk';\n\nimport blockLayout from './layouts/block';\nimport { ProgressTypes } from './constants';\n\nconst rootTemplate = document.createElement('template');\n\nrootTemplate.innerHTML = /*html*/ `\n<style>\n :host {\n display: flex;\n flex-direction: column;\n }\n\n mux-uploader-drop {\n flex-grow: 1;\n }\n\n input[type=\"file\"] {\n display: none;\n }\n</style>\n\n<input id=\"hidden-file-input\" type=\"file\" accept=\"video/*, audio/*\" />\n<mux-uploader-sr-text></mux-uploader-sr-text>\n`;\n\ntype Endpoint = UpChunk['endpoint'] | undefined | null;\ntype DynamicChunkSize = UpChunk['dynamicChunkSize'] | undefined;\n\ntype ErrorDetail = {\n message: string;\n chunkNumber?: number;\n attempts?: number;\n};\n\n// NOTE: Progress event is already determined on HTMLElement but have inconsistent types. Should consider renaming events (CJP)\nexport interface MuxUploaderElementEventMap extends Omit<HTMLElementEventMap, 'progress'> {\n uploadstart: CustomEvent<{ file: File; chunkSize: number }>;\n chunkattempt: CustomEvent<{\n chunkNumber: number;\n chunkSize: number;\n }>;\n chunksuccess: CustomEvent<{\n chunk: number;\n chunkSize: number;\n attempts: number;\n timeInterval: number;\n // Note: This should be more explicitly typed in Upchunk. (TD).\n response: any;\n }>;\n uploaderror: CustomEvent<ErrorDetail>;\n progress: CustomEvent<number>;\n success: CustomEvent<undefined | null>;\n 'file-ready': CustomEvent<File>;\n}\n\ninterface MuxUploaderElement extends HTMLElement {\n addEventListener<K extends keyof MuxUploaderElementEventMap>(\n type: K,\n listener: (this: HTMLMediaElement, ev: MuxUploaderElementEventMap[K]) => any,\n options?: boolean | AddEventListenerOptions\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions\n ): void;\n removeEventListener<K extends keyof MuxUploaderElementEventMap>(\n type: K,\n listener: (this: HTMLMediaElement, ev: MuxUploaderElementEventMap[K]) => any,\n options?: boolean | EventListenerOptions\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions\n ): void;\n}\n\nclass MuxUploaderElement extends globalThis.HTMLElement implements MuxUploaderElement {\n static get observedAttributes() {\n return [\n 'pausable',\n 'type',\n 'no-drop',\n 'no-progress',\n 'no-status',\n 'no-retry',\n 'max-file-size',\n 'use-large-file-workaround',\n ];\n }\n\n protected _endpoint: Endpoint;\n protected _upload?: UpChunk;\n\n constructor() {\n super();\n\n const shadow = this.attachShadow({ mode: 'open' });\n\n // Always attach the root template\n shadow.appendChild(rootTemplate.content.cloneNode(true));\n\n // Attach a layout\n this.updateLayout();\n\n this.hiddenFileInput?.addEventListener('change', () => {\n const file = this.hiddenFileInput?.files?.[0];\n this.toggleAttribute('file-ready', !!file);\n\n if (file) {\n this.dispatchEvent(\n new CustomEvent('file-ready', {\n composed: true,\n bubbles: true,\n detail: file,\n })\n );\n }\n });\n }\n\n connectedCallback() {\n this.addEventListener('file-ready', this.handleUpload);\n this.addEventListener('reset', this.resetState);\n }\n\n disconnectedCallback() {\n this.removeEventListener('file-ready', this.handleUpload, false);\n this.removeEventListener('reset', this.resetState);\n }\n\n attributeChangedCallback() {\n this.updateLayout();\n }\n\n protected get hiddenFileInput() {\n return this.shadowRoot?.querySelector('#hidden-file-input') as HTMLInputElement;\n }\n\n get endpoint(): Endpoint {\n return this.getAttribute('endpoint') ?? this._endpoint;\n }\n\n set endpoint(value: Endpoint) {\n if (value === this.endpoint) return;\n if (typeof value === 'string') {\n this.setAttribute('endpoint', value);\n } else if (value == undefined) {\n this.removeAttribute('endpoint');\n }\n this._endpoint = value;\n }\n\n get type() {\n return (this.getAttribute('type') ?? undefined) as ProgressTypes[keyof ProgressTypes] | undefined;\n }\n\n set type(val: ProgressTypes[keyof ProgressTypes] | undefined) {\n if (val == this.type) return;\n if (!val) {\n this.removeAttribute('type');\n } else {\n this.setAttribute('type', val);\n }\n }\n\n get noDrop(): boolean {\n return this.hasAttribute('no-drop');\n }\n\n set noDrop(value: boolean) {\n this.toggleAttribute('no-drop', Boolean(value));\n }\n\n get noProgress(): boolean {\n return this.hasAttribute('no-progress');\n }\n\n set noProgress(value: boolean) {\n this.toggleAttribute('no-progress', Boolean(value));\n }\n\n get noStatus(): boolean {\n return this.hasAttribute('no-status');\n }\n\n set noStatus(value: boolean) {\n this.toggleAttribute('no-status', Boolean(value));\n }\n\n get noRetry(): boolean {\n return this.hasAttribute('no-retry');\n }\n\n set noRetry(value: boolean) {\n this.toggleAttribute('no-retry', Boolean(value));\n }\n\n get pausable() {\n return this.hasAttribute('pausable');\n }\n\n set pausable(value) {\n this.toggleAttribute('pausable', Boolean(value));\n }\n\n get dynamicChunkSize(): DynamicChunkSize {\n return this.hasAttribute('dynamic-chunk-size');\n }\n\n set dynamicChunkSize(value: DynamicChunkSize) {\n if (value === this.hasAttribute('dynamic-chunk-size')) return;\n if (value) {\n this.setAttribute('dynamic-chunk-size', '');\n } else {\n this.removeAttribute('dynamic-chunk-size');\n }\n }\n\n get useLargeFileWorkaround() {\n return this.hasAttribute('use-large-file-workaround');\n }\n\n set useLargeFileWorkaround(value: boolean | undefined) {\n if (value == this.useLargeFileWorkaround) return;\n this.toggleAttribute('use-large-file-workaround', !!value);\n }\n\n get maxFileSize(): number | undefined {\n const maxFileSize = this.getAttribute('max-file-size');\n return maxFileSize !== null ? parseInt(maxFileSize) : undefined;\n }\n\n set maxFileSize(value: number | undefined) {\n if (value) {\n this.setAttribute('max-file-size', value.toString());\n } else {\n this.removeAttribute('max-file-size');\n }\n }\n\n get chunkSize(): number | undefined {\n const chunkSize = this.getAttribute('chunk-size');\n return chunkSize !== null ? parseInt(chunkSize) : undefined;\n }\n\n set chunkSize(value: number | undefined) {\n if (value) {\n this.setAttribute('chunk-size', value.toString());\n } else {\n this.removeAttribute('chunk-size');\n }\n }\n\n get upload() {\n return this._upload;\n }\n\n get paused() {\n return this.upload?.paused ?? false;\n }\n\n set paused(value) {\n if (!this.upload) {\n console.warn('Pausing before an upload has begun is unsupported');\n return;\n }\n const boolVal = !!value;\n if (boolVal === this.paused) return;\n if (boolVal) {\n this.upload.pause();\n } else {\n this.upload.resume();\n }\n this.toggleAttribute('paused', boolVal);\n this.dispatchEvent(new CustomEvent('pausedchange', { detail: boolVal }));\n }\n\n updateLayout() {\n const oldLayout = this.shadowRoot?.querySelector('mux-uploader-drop, div');\n if (oldLayout) {\n oldLayout.remove();\n }\n const newLayout = blockLayout(this);\n this.shadowRoot?.appendChild(newLayout);\n }\n\n setError(message: string) {\n this.setAttribute('upload-error', '');\n this.dispatchEvent(new CustomEvent('uploaderror', { detail: { message } }));\n }\n\n resetState() {\n this.removeAttribute('upload-error');\n this.removeAttribute('upload-in-progress');\n this.removeAttribute('upload-complete');\n // Reset file to ensure change/input events will fire, even if selecting the same file (CJP).\n this.hiddenFileInput.value = '';\n }\n\n handleUpload(evt: CustomEvent) {\n const endpoint = this.endpoint;\n const dynamicChunkSize = this.dynamicChunkSize;\n\n if (!endpoint) {\n this.setError(`No url or endpoint specified -- cannot handleUpload`);\n // Bail early if no endpoint.\n return;\n } else {\n this.removeAttribute('upload-error');\n }\n\n try {\n const upload = UpChunk.createUpload({\n endpoint,\n dynamicChunkSize,\n file: evt.detail,\n maxFileSize: this.maxFileSize,\n chunkSize: this.chunkSize,\n useLargeFileWorkaround: this.useLargeFileWorkaround,\n });\n\n this._upload = upload;\n\n this.dispatchEvent(\n new CustomEvent('uploadstart', { detail: { file: upload.file, chunkSize: upload.chunkSize } })\n );\n this.setAttribute('upload-in-progress', '');\n\n if (upload.offline) {\n this.dispatchEvent(new CustomEvent('offline'));\n }\n\n upload.on('attempt', (event: any) => {\n this.dispatchEvent(new CustomEvent('chunkattempt', event));\n });\n\n upload.on('chunkSuccess', (event: any) => {\n this.dispatchEvent(new CustomEvent('chunksuccess', event));\n });\n\n upload.on('error', (event: any) => {\n this.setAttribute('upload-error', '');\n console.error('error handler', event.detail.message);\n this.dispatchEvent(new CustomEvent('uploaderror', event));\n });\n\n upload.on('progress', (event: any) => {\n this.dispatchEvent(new CustomEvent('progress', event));\n });\n\n upload.on('success', (event: any) => {\n this.removeAttribute('upload-in-progress');\n this.setAttribute('upload-complete', '');\n\n this.dispatchEvent(new CustomEvent('success', event));\n });\n\n upload.on('offline', (event: any) => {\n this.dispatchEvent(new CustomEvent('offline', event));\n });\n upload.on('online', (event: any) => {\n this.dispatchEvent(new CustomEvent('online', event));\n });\n } catch (err) {\n if (err instanceof Error) {\n this.setError(err.message);\n }\n }\n }\n}\n\ntype MuxUploaderElementType = typeof MuxUploaderElement;\ndeclare global {\n // eslint-disable-next-line\n var MuxUploaderElement: MuxUploaderElementType;\n}\n\nif (!globalThis.customElements.get('mux-uploader')) {\n globalThis.customElements.define('mux-uploader', MuxUploaderElement);\n globalThis.MuxUploaderElement = MuxUploaderElement;\n}\n\nexport default MuxUploaderElement;\n", "import type MuxUploaderElement from '../mux-uploader';\n\nexport const closestComposedNode = (childNode: Element, selector: string): HTMLElement | null => {\n if (!childNode) return null;\n const closest = childNode.closest<HTMLElement>(selector);\n if (closest) return closest;\n return closestComposedNode((childNode.getRootNode() as ShadowRoot).host, selector);\n};\n\nexport const getMuxUploaderEl = (controlEl: Element): MuxUploaderElement | null => {\n const muxUploaderId = controlEl.getAttribute('mux-uploader');\n if (muxUploaderId) {\n return document.getElementById(muxUploaderId) as MuxUploaderElement;\n }\n return closestComposedNode(controlEl, 'mux-uploader') as MuxUploaderElement;\n};\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport type MuxUploaderElement from './mux-uploader';\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = /*html*/ `\n<style>\n :host {\n position: relative;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n border: 2px dashed #ccc;\n padding: 2.5rem 2rem;\n border-radius: .25rem;\n }\n\n slot[name='heading'] > * {\n margin-bottom: 0.75rem;\n font-size: 1.75rem;\n text-align: center;\n }\n\n slot[name='separator'] > * {\n margin-bottom: 0.75rem;\n }\n\n #overlay {\n display: none;\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n left: 0;\n height: 100%;\n width: 100%;\n }\n\n :host([active][overlay]) > #overlay {\n background: var(--overlay-background-color, rgba(226, 253, 255, 0.95));\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n }\n\n :host([file-ready])::part(heading),\n :host([file-ready])::part(separator) {\n display: none;\n }\n</style>\n\n<slot name=\"heading\" part=\"heading\">\n <span>Drop a video file here to upload</span>\n</slot>\n<slot name=\"separator\" part=\"separator\">\n <span>or</span>\n</slot>\n<slot></slot>\n\n<div id=\"overlay\">\n <h1 id=\"overlay-label\"></h1>\n</div>\n`;\n\nconst Attributes = {\n MUX_UPLOADER: 'mux-uploader',\n OVERLAY_TEXT: 'overlay-text',\n};\n\nclass MuxUploaderDropElement extends globalThis.HTMLElement {\n #overlayTextEl: HTMLElement;\n #uploaderEl: MuxUploaderElement | null | undefined;\n\n #abortController: AbortController | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n this.#overlayTextEl = shadowRoot.getElementById('overlay-label') as HTMLElement;\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n\n this.#uploaderEl.addEventListener('file-ready', () => this.toggleAttribute('file-ready', true), opts);\n this.#uploaderEl.addEventListener('uploadstart', () => this.toggleAttribute('upload-in-progress', true), opts);\n this.#uploaderEl.addEventListener(\n 'success',\n () => {\n this.toggleAttribute('upload-in-progress', false);\n this.toggleAttribute('upload-complete', true);\n },\n opts\n );\n this.#uploaderEl.addEventListener(\n 'reset',\n () => {\n this.toggleAttribute('file-ready', false);\n this.toggleAttribute('upload-in-progress', false);\n this.toggleAttribute('upload-complete', false);\n },\n opts\n );\n\n this.setupDragEvents(opts);\n\n this.toggleAttribute('upload-in-progress', this.#uploaderEl.hasAttribute('upload-in-progress'));\n this.toggleAttribute('upload-complete', this.#uploaderEl.hasAttribute('upload-complete'));\n this.toggleAttribute('file-ready', this.#uploaderEl.hasAttribute('file-ready'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n attributeChangedCallback(attributeName: string, oldValue: string | null, newValue: string | null) {\n if (attributeName === Attributes.OVERLAY_TEXT && oldValue !== newValue) {\n this.#overlayTextEl.innerHTML = newValue ?? '';\n } else if (attributeName === 'active') {\n if (this.hasAttribute('overlay') && newValue != null) {\n this._currentDragTarget = this;\n }\n }\n }\n\n static get observedAttributes() {\n return [Attributes.OVERLAY_TEXT, Attributes.MUX_UPLOADER, 'active'];\n }\n\n protected _currentDragTarget?: Node;\n\n setupDragEvents(opts: AddEventListenerOptions) {\n this.addEventListener(\n 'dragenter',\n (evt) => {\n this._currentDragTarget = evt.target as Node;\n evt.preventDefault();\n evt.stopPropagation();\n this.toggleAttribute('active', true);\n },\n opts\n );\n\n this.addEventListener(\n 'dragleave',\n (evt) => {\n if (this._currentDragTarget === evt.target) {\n this._currentDragTarget = undefined;\n this.toggleAttribute('active', false);\n }\n },\n opts\n );\n\n this.addEventListener(\n 'dragover',\n (evt) => {\n evt.preventDefault();\n evt.stopPropagation();\n },\n opts\n );\n\n this.addEventListener(\n 'drop',\n (evt) => {\n evt.preventDefault();\n evt.stopPropagation();\n const { dataTransfer } = evt;\n //@ts-ignore\n const { files } = dataTransfer;\n const file = files[0];\n\n const uploaderController = this.#uploaderEl ?? this;\n\n uploaderController.dispatchEvent(\n new CustomEvent('file-ready', {\n composed: true,\n bubbles: true,\n detail: file,\n })\n );\n\n this.removeAttribute('active');\n },\n opts\n );\n }\n}\n\nif (!globalThis.customElements.get('mux-uploader-drop')) {\n globalThis.customElements.define('mux-uploader-drop', MuxUploaderDropElement);\n //@ts-ignore\n globalThis.MuxUploaderDropElement = MuxUploaderDropElement;\n}\n\nexport default MuxUploaderDropElement;\n", "export function formatProgress(percent: number): string {\n return `${Math.floor(percent)}%`;\n}\n", "import { globalThis, document } from './polyfills';\nimport { ProgressTypes } from './constants';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport { formatProgress } from './utils/progress';\n\nconst template = document.createElement('template');\nconst ariaDescription = 'Media upload progress bar';\n\ntemplate.innerHTML = /*html*/ `\n<style>\n :host {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n }\n\n .bar-type {\n background: var(--progress-bar-background-color, #e6e6e6);\n border-radius: var(--progress-bar-border-radius, 100px);\n height: var(--progress-bar-height, 4px);\n width: 100%;\n }\n\n .radial-type,\n .bar-type,\n #percentage-type,\n :host([type=\"bar\"][upload-error]) #percentage-type {\n display: none;\n }\n\n :host([type=\"radial\"][upload-in-progress]) .radial-type,\n :host([type=\"bar\"][upload-in-progress]) .bar-type {\n display: block;\n }\n\n :host([type=\"percentage\"][upload-in-progress]) #percentage-type {\n display: var(--progress-percentage-display, block);\n }\n\n :host([type=\"bar\"][upload-error]) .progress-bar {\n background: #e22c3e;\n }\n\n .progress-bar {\n box-shadow: var(--progress-bar-box-shadow, 0 10px 40px -10px #fff);\n border-radius: var(--progress-bar-border-radius, 100px);\n background: var(--progress-bar-fill-color, #000000);\n height: var(--progress-bar-height, 4px);\n width: 0%;\n transition: width 0.25s;\n }\n\n circle {\n stroke: var(--progress-radial-fill-color, black);\n stroke-width: 6; /* Thickness of the circle */\n fill: transparent; /* Make inside of the circle see-through */\n\n /* Animation */\n transition: 0.35s;\n transform: rotate(-90deg);\n transform-origin: 50% 50%;\n -webkit-transform-origin: 50% 50%;\n -moz-transform-origin: 50% 50%;\n }\n\n #percentage-type {\n font-size: inherit;\n margin: 0 0 1em;\n }\n</style>\n\n<slot></slot>\n\n<p id=\"percentage-type\"></p>\n<div class=\"bar-type\">\n <div role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" class=\"progress-bar\" id=\"progress-bar\" tabindex=\"0\"></div>\n</div>\n<div class=\"radial-type\">\n <svg\n width=\"120\"\n height=\"120\">\n <!-- To prevent overflow of the SVG wrapper, radius must be (svgWidth / 2) - (circleStrokeWidth * 2)\n or use overflow: visible on the svg.-->\n <circle\n r=\"52\"\n cx=\"60\"\n cy=\"60\"\n />\n <svg>\n</div>\n`;\n\nclass MuxUploaderProgressElement extends globalThis.HTMLElement {\n #uploaderEl: HTMLElement | null | undefined;\n #abortController: AbortController | undefined;\n\n svgCircle: SVGCircleElement | null | undefined;\n progressBar: HTMLElement | null | undefined;\n uploadPercentage: HTMLElement | null | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n this.svgCircle = this.shadowRoot?.querySelector('circle');\n this.progressBar = this.shadowRoot?.getElementById('progress-bar');\n this.uploadPercentage = this.shadowRoot?.getElementById('percentage-type');\n this.progressBar?.setAttribute('aria-description', ariaDescription);\n }\n\n connectedCallback() {\n this.setDefaultType();\n\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n\n this.#uploaderEl.addEventListener('uploadstart', this.onUploadStart, opts);\n this.#uploaderEl.addEventListener('reset', this.onReset);\n this.#uploaderEl.addEventListener('progress', this.onProgress);\n this.#uploaderEl.addEventListener('success', this.onSuccess);\n this.toggleAttribute('upload-in-progress', this.#uploaderEl.hasAttribute('upload-in-progress'));\n this.toggleAttribute('upload-complete', this.#uploaderEl.hasAttribute('upload-complete'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n onUploadStart = () => {\n this.progressBar?.focus();\n this.toggleAttribute('upload-in-progress', true);\n };\n\n onProgress = (e: Event) => {\n // @ts-ignore\n const percent = e.detail;\n this.progressBar?.setAttribute('aria-valuenow', `${Math.floor(percent)}`);\n\n switch (this.getAttribute('type')) {\n case ProgressTypes.BAR: {\n if (this.progressBar) {\n this.progressBar.style.width = `${percent}%`;\n }\n break;\n }\n case ProgressTypes.RADIAL: {\n if (this.svgCircle) {\n // The closer the upload percentage gets to 100%, the closer offset gets to 0.\n // The closer offset gets to 0, the more we can see the circumference of our circle. (TD).\n const offset = this.getCircumference() - (percent / 100) * this.getCircumference();\n\n this.svgCircle.style.strokeDashoffset = offset.toString();\n }\n break;\n }\n case ProgressTypes.PERCENTAGE: {\n if (this.uploadPercentage) this.uploadPercentage.innerHTML = formatProgress(percent);\n break;\n }\n }\n };\n\n onSuccess = () => {\n this.toggleAttribute('upload-in-progress', false);\n this.toggleAttribute('upload-complete', true);\n };\n\n onReset = () => {\n this.toggleAttribute('upload-in-progress', false);\n if (this.uploadPercentage) {\n this.uploadPercentage.innerHTML = '';\n }\n\n if (this.svgCircle) {\n this.svgCircle.style.strokeDashoffset = `${this.getCircumference()}`;\n }\n };\n\n getRadius() {\n return Number(this.svgCircle?.getAttribute('r'));\n }\n\n getCircumference() {\n return this.getRadius() * 2 * Math.PI;\n }\n\n setDefaultType() {\n const currentType = this.getAttribute('type');\n\n if (!currentType) {\n this.setAttribute('type', ProgressTypes.BAR);\n }\n\n if (currentType === ProgressTypes.RADIAL && this.svgCircle) {\n // strokeDasharray is the size of dashes used to draw the circle with the size of gaps in between.\n // If the dash number is the same as the gap number, no gap is visible: a full circle.\n // strokeDashoffset defines where along our circle the dashes (in our case, a dash as long as the\n // circumference of our circle) begins. The larger the offset, the farther into the circle you're\n // starting the \"dash\". In the beginning, offset is the same as the circumference. Meaning, the visible\n // dash starts at the end so we don't see the full circle. Instead we see a gap the size of the circle.\n // When the percentage is 100%, offset is 0 meaning the dash starts at the beginning so we can see the circle. (TD).\n this.svgCircle.style.strokeDasharray = `${this.getCircumference()} ${this.getCircumference()}`;\n this.svgCircle.style.strokeDashoffset = `${this.getCircumference()}`;\n }\n }\n}\n\nif (!globalThis.customElements.get('mux-uploader-progress')) {\n globalThis.customElements.define('mux-uploader-progress', MuxUploaderProgressElement);\n}\n\nexport default MuxUploaderProgressElement;\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport { type MuxUploaderElementEventMap } from './mux-uploader';\nimport type MuxUploaderElement from './mux-uploader';\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = `\n<style>\n\n:host([upload-error]) {\n color: #e22c3e;\n}\n</style>\n\n<span id=\"status-message\" role=\"status\" aria-live=\"polite\"></span>\n`;\n\nclass MuxUploaderStatusElement extends globalThis.HTMLElement {\n statusMessage: HTMLElement | null | undefined;\n #uploaderEl: MuxUploaderElement | null | undefined;\n #abortController: AbortController | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n this.statusMessage = this.shadowRoot?.getElementById('status-message');\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n this.#uploaderEl.addEventListener('reset', this.clearStatusMessage, opts);\n this.#uploaderEl.addEventListener('uploaderror', this.onUploadError, opts);\n this.#uploaderEl.addEventListener('success', this.onSuccess, opts);\n this.#uploaderEl.addEventListener('uploadstart', this.clearStatusMessage, opts);\n this.#uploaderEl.addEventListener('offline', this.onOffline, opts);\n this.#uploaderEl.addEventListener('online', this.clearStatusMessage, opts);\n\n this.toggleAttribute('upload-in-progress', this.#uploaderEl.hasAttribute('upload-in-progress'));\n this.toggleAttribute('upload-complete', this.#uploaderEl.hasAttribute('upload-complete'));\n this.toggleAttribute('upload-error', this.#uploaderEl.hasAttribute('upload-error'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n clearStatusMessage = () => {\n this.toggleAttribute('upload-error', false);\n if (this.statusMessage) {\n this.statusMessage.innerHTML = '';\n }\n };\n\n onUploadError = (e: MuxUploaderElementEventMap['uploaderror']) => {\n this.toggleAttribute('upload-error', true);\n if (this.statusMessage) {\n this.statusMessage.innerHTML = e.detail.message;\n }\n };\n\n onSuccess = () => {\n this.toggleAttribute('upload-error', false);\n const successMessage = 'Upload complete!';\n\n if (this.statusMessage) {\n this.statusMessage.innerHTML = successMessage;\n }\n\n console.info(successMessage);\n };\n\n onOffline = () => {\n this.toggleAttribute('upload-error', false);\n const offlineMessage = 'Currently offline. Upload will resume automatically when online.';\n\n if (this.statusMessage) {\n this.statusMessage.innerHTML = offlineMessage;\n }\n };\n}\n\nif (!globalThis.customElements.get('mux-uploader-status')) {\n globalThis.customElements.define('mux-uploader-status', MuxUploaderStatusElement);\n}\n\nexport default MuxUploaderStatusElement;\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport type MuxUploaderElement from './mux-uploader';\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = `\n<style>\n #retry-button {\n color: #e22c3e;\n text-decoration-line: underline;\n cursor: pointer;\n position: relative;\n display: none;\n }\n\n :host([upload-error]) #retry-button {\n display: inline-block;\n }\n</style>\n\n<span id=\"retry-button\" role=\"button\" tabindex=\"0\">Try again</span>\n`;\n\nclass MuxUploaderRetryElement extends globalThis.HTMLElement {\n retryButton: HTMLElement | null | undefined;\n #uploaderEl: MuxUploaderElement | null | undefined;\n #abortController: AbortController | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n this.retryButton = this.shadowRoot?.getElementById('retry-button');\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n this.#uploaderEl.addEventListener('uploaderror', () => this.toggleAttribute('upload-error', true));\n this.#uploaderEl.addEventListener('reset', () => this.toggleAttribute('upload-error', false));\n this.retryButton?.addEventListener('click', this.triggerReset, opts);\n this.retryButton?.addEventListener('keyup', this.handleKeyup, opts);\n\n this.toggleAttribute('upload-error', this.#uploaderEl.hasAttribute('upload-error'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n handleKeyup = (e: KeyboardEvent) => {\n const ButtonPressedKeys = ['Enter', ' '];\n const { key } = e;\n if (!ButtonPressedKeys.includes(key)) {\n return;\n }\n\n this.triggerReset();\n };\n\n triggerReset = () => {\n this.#uploaderEl?.dispatchEvent(new CustomEvent('reset'));\n };\n}\n\nif (!globalThis.customElements.get('mux-uploader-retry')) {\n globalThis.customElements.define('mux-uploader-retry', MuxUploaderRetryElement);\n}\n\nexport default MuxUploaderRetryElement;\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport type MuxUploaderElement from './mux-uploader';\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = /*html*/ `\n<style>\n#pause-button {\n cursor: pointer;\n line-height: 16px;\n background: #fff;\n border: 1px solid #000;\n color: #000000;\n padding: 16px 24px;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease;\n transition: all 0.2s ease;\n font-family: inherit;\n font-size: inherit;\n position: relative;\n display: none;\n}\n\n#pause-button:hover:not(:disabled) {\n color: #fff;\n background: #404040;\n}\n\n#pause-button:active {\n color: #fff;\n background: #000;\n}\n\n#pause-button:disabled {\n cursor: not-allowed;\n}\n\n:host([upload-in-progress]:not([upload-error], [upload-complete])) #pause-button {\n display: initial;\n}\n</style>\n\n<button id=\"pause-button\">Pause</span>\n`;\n\nclass MuxUploaderPauseElement extends globalThis.HTMLElement {\n #uploaderEl: MuxUploaderElement | null | undefined;\n #abortController: AbortController | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n this.#uploaderEl.addEventListener('uploadstart', () => this.toggleAttribute('upload-in-progress', true), opts);\n this.#uploaderEl.addEventListener('uploaderror', () => {\n this.toggleAttribute('upload-error', true);\n this.toggleAttribute('upload-complete', false);\n this.toggleAttribute('upload-in-progress', false);\n });\n this.#uploaderEl.addEventListener('success', () => {\n this.toggleAttribute('upload-complete', true);\n this.toggleAttribute('upload-error', false);\n this.toggleAttribute('upload-in-progress', false);\n });\n this.#uploaderEl.addEventListener('reset', () => {\n this.toggleAttribute('upload-error', false);\n this.toggleAttribute('upload-in-progress', false);\n this.toggleAttribute('upload-complete', false);\n });\n /** @TODO Implement a more robust \"pausedState\" in mux-uploader (plausibly in upchunk) to account for \"pausing\" (CJP) */\n this.#uploaderEl.addEventListener('pausedchange', () => {\n this.pauseButton.disabled = false;\n if (!this.#uploaderEl) return;\n const nextPausedState = this.#uploaderEl.paused ?? false;\n // If entered paused, currently does not take effect until current chunk completes upload,\n // so show as \"pausing\"\n this.pauseButton.innerHTML = nextPausedState ? 'Pausing...' : 'Pause';\n if (nextPausedState) {\n this.pauseButton.disabled = true;\n this.#uploaderEl.addEventListener(\n 'chunksuccess',\n () => {\n // Recheck paused state just in case state changed while waiting for 'chunksuccess'\n this.pauseButton.innerHTML = this.#uploaderEl?.paused ? 'Resume' : 'Pause';\n this.pauseButton.disabled = false;\n },\n { once: true }\n );\n }\n });\n\n this.pauseButton.addEventListener('click', this.triggerPause, opts);\n\n this.toggleAttribute('upload-in-progress', this.#uploaderEl.hasAttribute('upload-in-progress'));\n this.toggleAttribute('upload-complete', this.#uploaderEl.hasAttribute('upload-complete'));\n this.toggleAttribute('upload-error', this.#uploaderEl.hasAttribute('upload-error'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n get pauseButton() {\n return this.shadowRoot?.getElementById('pause-button') as HTMLButtonElement;\n }\n\n triggerPause = () => {\n if (!this.#uploaderEl) {\n console.warn('pausing before a mux-uploader element is associated is unsupported!');\n return;\n }\n if (this.pauseButton.disabled) {\n return;\n }\n this.#uploaderEl.paused = !this.#uploaderEl.paused;\n };\n}\n\nif (!globalThis.customElements.get('mux-uploader-pause')) {\n globalThis.customElements.define('mux-uploader-pause', MuxUploaderPauseElement);\n}\n\nexport default MuxUploaderPauseElement;\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\nimport type MuxUploaderElement from './mux-uploader';\n\nexport const fileSelectFragment = /*html*/ `\n <style>\n #file-select {\n cursor: pointer;\n line-height: 16px;\n background: #fff;\n border: 1px solid #000;\n color: #000000;\n padding: 16px 24px;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease;\n transition: all 0.2s ease;\n font-family: inherit;\n font-size: inherit;\n position: relative;\n }\n\n #file-select:hover {\n color: #fff;\n background: #404040;\n }\n\n #file-select:active {\n color: #fff;\n background: #000;\n }\n\n </style>\n\n <button id=\"file-select\" type=\"button\" part=\"file-select-button\">Upload a video</button>\n`;\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = /*html*/ `\n <style>\n :host { display: inline-block; }\n\n :host([file-ready]) > slot {\n display: none;\n }\n </style>\n\n <slot>\n ${fileSelectFragment}\n </slot>\n`;\n\nclass MuxUploaderFileSelectElement extends globalThis.HTMLElement {\n #filePickerEl: HTMLElement | null | undefined;\n #uploaderEl: MuxUploaderElement | null | undefined;\n\n #abortController: AbortController | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n // NOTE: Binding this so that we have a reference to remove the event listener\n // but can still reference `this` in the method. (CJP)\n this.handleFilePickerElClick = this.handleFilePickerElClick.bind(this);\n\n // Since we have a \"default slotted\" element, we still need to initialize the slottable elements\n // (Note the difference in selectors and related code in 'slotchange' handler, below)\n this.filePickerEl = this.shadowRoot?.querySelector('button');\n\n this.shadowRoot?.querySelector('slot')?.addEventListener('slotchange', (e) => {\n const slot = e.currentTarget as HTMLSlotElement;\n this.filePickerEl = slot\n .assignedElements({ flatten: true })\n .filter((el) => !['STYLE'].includes(el.nodeName))[0] as HTMLButtonElement;\n });\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n this.#abortController = new AbortController();\n\n if (this.#uploaderEl) {\n const opts = { signal: this.#abortController.signal };\n\n this.#uploaderEl.addEventListener(\n 'file-ready',\n () => {\n this.toggleAttribute('file-ready', true);\n },\n opts\n );\n\n this.#uploaderEl.addEventListener('uploadstart', () => this.toggleAttribute('upload-in-progress', true), opts);\n\n this.#uploaderEl.addEventListener(\n 'success',\n () => {\n this.toggleAttribute('upload-in-progress', false);\n this.toggleAttribute('upload-complete', true);\n },\n opts\n );\n\n this.#uploaderEl.addEventListener(\n 'reset',\n () => {\n this.toggleAttribute('file-ready', false);\n },\n opts\n );\n\n this.toggleAttribute('upload-in-progress', this.#uploaderEl.hasAttribute('upload-in-progress'));\n this.toggleAttribute('upload-complete', this.#uploaderEl.hasAttribute('upload-complete'));\n this.toggleAttribute('file-ready', this.#uploaderEl.hasAttribute('file-ready'));\n }\n }\n\n disconnectedCallback() {\n this.#abortController?.abort();\n }\n\n protected get filePickerEl() {\n return this.#filePickerEl;\n }\n\n protected set filePickerEl(value: HTMLElement | null | undefined) {\n if (value === this.#filePickerEl) return;\n if (this.#filePickerEl) {\n this.#filePickerEl.removeEventListener('click', this.handleFilePickerElClick);\n }\n\n this.#filePickerEl = value;\n if (this.#filePickerEl) {\n this.#filePickerEl.addEventListener('click', this.handleFilePickerElClick);\n }\n }\n\n handleFilePickerElClick() {\n // TO-DO: Allow user to reattempt uploading the same file after an error.\n // Note: Apparently Chrome and Firefox do not allow changing an indexed property on FileList...(TD).\n // Source: https://stackoverflow.com/a/46689013\n const attr = this.getAttribute('mux-uploader');\n const controller = attr ? document.getElementById(attr) : (this.getRootNode() as ShadowRoot).host;\n\n controller?.shadowRoot?.querySelector<HTMLInputElement>('#hidden-file-input')?.click();\n }\n}\n\nif (!globalThis.customElements.get('mux-uploader-file-select')) {\n globalThis.customElements.define('mux-uploader-file-select', MuxUploaderFileSelectElement);\n}\n\nexport default MuxUploaderFileSelectElement;\n", "import { document } from '../polyfills';\n\nimport '../mux-uploader-drop';\nimport '../mux-uploader-progress';\nimport '../mux-uploader-status';\nimport '../mux-uploader-retry';\nimport '../mux-uploader-pause';\nimport '../mux-uploader-file-select';\nimport { fileSelectFragment } from '../mux-uploader-file-select';\nimport MuxUploaderElement from '../mux-uploader';\n\nfunction conditionalRender(flag: boolean | undefined, component: string): string {\n return flag ? '' : component;\n}\n\nconst attributeRender = (name: string, value: any): string => {\n if (value == null || value === false) return '';\n const valueStr = value === true ? '' : `${value}`;\n return `${name}=\"${valueStr}\"`;\n};\n\nexport default function blockLayout(contextElement: MuxUploaderElement): DocumentFragment {\n const { noDrop, noProgress, noStatus, noRetry, pausable, type } = contextElement;\n const wrapper = noDrop ? 'div' : 'mux-uploader-drop overlay part=\"drop\"';\n const progressElements = conditionalRender(\n noProgress,\n `\n <mux-uploader-progress part=\"progress progress-percentage\" type=\"percentage\"></mux-uploader-progress>\n <mux-uploader-progress part=\"progress progress-bar\" ${attributeRender('type', type)}></mux-uploader-progress>\n `\n );\n const statusElement = conditionalRender(noStatus, '<mux-uploader-status part=\"status\"></mux-uploader-status>');\n const retryElement = conditionalRender(noRetry, '<mux-uploader-retry part=\"retry\"></mux-uploader-retry>');\n const pauseElement = conditionalRender(!pausable, '<mux-uploader-pause part=\"pause\"></mux-uploader-pause>');\n\n return document.createRange().createContextualFragment(`\n <${wrapper}>\n ${statusElement}\n ${retryElement}\n ${pauseElement}\n\n <mux-uploader-file-select part=\"file-select\">\n <slot name=\"file-select\">\n ${fileSelectFragment}\n </slot>\n </mux-uploader-file-select>\n\n ${progressElements}\n </${wrapper}>\n `);\n}\n", "import { globalThis, document } from './polyfills';\nimport { getMuxUploaderEl } from './utils/element-utils';\n\nconst template = document.createElement('template');\n\ntemplate.innerHTML = `\n<style>\n\n.sr-only {\n position:absolute;\n left:-10000px;\n top:auto;\n width:1px;\n height:1px;\n overflow:hidden;\n}\n</style>\n\n<div class=\"sr-only\" id=\"sr-only\" aria-live=\"polite\"></div>\n`;\n\nclass MuxUploaderSrTextElement extends globalThis.HTMLElement {\n srOnlyText: HTMLElement | null | undefined;\n #uploaderEl: HTMLElement | null | undefined;\n\n constructor() {\n super();\n const shadowRoot = this.attachShadow({ mode: 'open' });\n shadowRoot.appendChild(template.content.cloneNode(true));\n\n this.srOnlyText = this.shadowRoot?.getElementById('sr-only');\n }\n\n connectedCallback() {\n this.#uploaderEl = getMuxUploaderEl(this);\n\n if (this.#uploaderEl) {\n this.#uploaderEl.addEventListener('success', this.updateText.bind(this));\n }\n }\n\n disconnectedCallback() {\n if (this.#uploaderEl) {\n this.#uploaderEl.removeEventListener('success', this.updateText.bind(this));\n }\n }\n\n updateText() {\n if (this.srOnlyText) {\n this.srOnlyText.textContent = 'Upload complete!';\n }\n }\n}\n\nif (!globalThis.customElements.get('mux-uploader-sr-text')) {\n globalThis.customElements.define('mux-uploader-sr-text', MuxUploaderSrTextElement);\n}\n\nexport default MuxUploaderSrTextElement;\n", "export * as constants from './constants';\nimport MuxUploaderElement, { MuxUploaderElementEventMap } from './mux-uploader';\nimport MuxUploaderProgressElement from './mux-uploader-progress';\nimport MuxUploaderDropElement from './mux-uploader-drop';\nimport MuxUploaderFileSelectElement from './mux-uploader-file-select';\nimport MuxUploaderRetryElement from './mux-uploader-retry';\nimport MuxUploaderSrTextElement from './mux-uploader-sr-text';\nimport MuxUploaderStatusElement from './mux-uploader-status';\nimport MuxUploaderPauseElement from './mux-uploader-pause';\n\nexport {\n MuxUploaderDropElement,\n MuxUploaderProgressElement,\n MuxUploaderFileSelectElement,\n MuxUploaderRetryElement,\n MuxUploaderSrTextElement,\n MuxUploaderStatusElement,\n MuxUploaderPauseElement,\n};\nexport type { MuxUploaderElementEventMap };\nexport default MuxUploaderElement;\n"], "mappings": "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