@liwe3/webcomponents 1.0.14 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AITextEditor.d.ts +173 -0
- package/dist/AITextEditor.d.ts.map +1 -0
- package/dist/ChunkUploader.d.ts +103 -0
- package/dist/ChunkUploader.d.ts.map +1 -0
- package/dist/ChunkUploader.js +614 -0
- package/dist/ChunkUploader.js.map +1 -0
- package/dist/ContainerBox.d.ts +112 -0
- package/dist/ContainerBox.d.ts.map +1 -0
- package/dist/ContainerBox.js +359 -0
- package/dist/ContainerBox.js.map +1 -0
- package/dist/DateSelector.d.ts +103 -0
- package/dist/DateSelector.d.ts.map +1 -0
- package/dist/Drawer.d.ts +63 -0
- package/dist/Drawer.d.ts.map +1 -0
- package/dist/Drawer.js +340 -0
- package/dist/Drawer.js.map +1 -0
- package/dist/ImageView.d.ts +42 -0
- package/dist/ImageView.d.ts.map +1 -0
- package/dist/ImageView.js +209 -0
- package/dist/ImageView.js.map +1 -0
- package/dist/PopoverMenu.d.ts +103 -0
- package/dist/PopoverMenu.d.ts.map +1 -0
- package/dist/SmartSelect.d.ts +99 -0
- package/dist/SmartSelect.d.ts.map +1 -0
- package/dist/Toast.d.ts +127 -0
- package/dist/Toast.d.ts.map +1 -0
- package/dist/Toast.js +79 -49
- package/dist/Toast.js.map +1 -1
- package/dist/TreeView.d.ts +84 -0
- package/dist/TreeView.d.ts.map +1 -0
- package/dist/TreeView.js +478 -0
- package/dist/TreeView.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -12
- package/dist/index.js.map +1 -1
- package/package.json +28 -3
- package/src/ChunkUploader.ts +921 -0
- package/src/ContainerBox.ts +570 -0
- package/src/Drawer.ts +435 -0
- package/src/ImageView.ts +265 -0
- package/src/Toast.ts +96 -32
- package/src/TreeView.ts +673 -0
- package/src/index.ts +45 -6
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChunkUploader.js","sources":["../src/ChunkUploader.ts"],"sourcesContent":["/**\n * ChunkUploader Web Component\n * A file uploader with drag & drop, file previews, and chunked upload support\n */\n\nexport interface UploadedFile {\n id: string;\n file: File;\n status: 'pending' | 'uploading' | 'completed' | 'error' | 'aborted';\n progress: number;\n uploadedBytes: number;\n preview?: string;\n error?: string;\n uploadId?: string; // R2 upload ID for aborting\n key?: string; // R2 key for aborting\n}\n\nexport interface ChunkUploaderConfig {\n serverURL: string;\n chunkSize: number; // in MB\n authToken?: string;\n validFiletypes?: string[]; // Array of extensions like ['jpg', 'png', 'pdf']\n maxFileSize?: number; // in MB\n onfilecomplete?: ( file: UploadedFile ) => void;\n onuploadcomplete?: ( files: UploadedFile[] ) => void;\n}\n\nconst DEFAULT_CHUNK_SIZE = 5; // 5MB - R2/S3 minimum part size (except last part)\nconst DEFAULT_MAX_FILE_SIZE = 5120; // 5GB in MB\n\nexport class ChunkUploaderElement extends HTMLElement {\n declare shadowRoot: ShadowRoot;\n private files: Map<string, UploadedFile> = new Map();\n private config: ChunkUploaderConfig = {\n serverURL: '',\n chunkSize: DEFAULT_CHUNK_SIZE,\n maxFileSize: DEFAULT_MAX_FILE_SIZE\n };\n private isUploading = false;\n private abortController: AbortController | null = null;\n\n constructor () {\n super();\n this.attachShadow( { mode: 'open' } );\n this.render();\n this.bindEvents();\n }\n\n static get observedAttributes (): string[] {\n return [ 'server-url', 'chunk-size', 'auth-token', 'valid-filetypes', 'max-file-size' ];\n }\n\n attributeChangedCallback ( name: string, oldValue: string | null, newValue: string | null ): void {\n if ( oldValue !== newValue ) {\n switch ( name ) {\n case 'server-url':\n this.config.serverURL = newValue || '';\n break;\n case 'chunk-size':\n this.config.chunkSize = parseFloat( newValue || String( DEFAULT_CHUNK_SIZE ) );\n break;\n case 'auth-token':\n this.config.authToken = newValue || undefined;\n break;\n case 'valid-filetypes':\n this.config.validFiletypes = newValue ? newValue.split( ',' ).map( ext => ext.trim() ) : undefined;\n break;\n case 'max-file-size':\n this.config.maxFileSize = parseFloat( newValue || String( DEFAULT_MAX_FILE_SIZE ) );\n break;\n }\n }\n }\n\n // Property getters and setters\n get serverURL (): string {\n return this.config.serverURL;\n }\n\n set serverURL ( value: string ) {\n this.config.serverURL = value;\n this.setAttribute( 'server-url', value );\n }\n\n get chunkSize (): number {\n return this.config.chunkSize;\n }\n\n set chunkSize ( value: number ) {\n this.config.chunkSize = value;\n this.setAttribute( 'chunk-size', value.toString() );\n }\n\n get authToken (): string | undefined {\n return this.config.authToken;\n }\n\n set authToken ( value: string | undefined ) {\n if ( value ) {\n this.config.authToken = value;\n this.setAttribute( 'auth-token', value );\n } else {\n this.config.authToken = undefined;\n this.removeAttribute( 'auth-token' );\n }\n }\n\n get validFiletypes (): string[] | undefined {\n return this.config.validFiletypes;\n }\n\n set validFiletypes ( value: string[] | undefined ) {\n this.config.validFiletypes = value;\n if ( value ) {\n this.setAttribute( 'valid-filetypes', value.join( ',' ) );\n } else {\n this.removeAttribute( 'valid-filetypes' );\n }\n }\n\n get maxFileSize (): number {\n return this.config.maxFileSize || DEFAULT_MAX_FILE_SIZE;\n }\n\n set maxFileSize ( value: number ) {\n this.config.maxFileSize = value;\n this.setAttribute( 'max-file-size', value.toString() );\n }\n\n set onfilecomplete ( callback: ( ( file: UploadedFile ) => void ) | undefined ) {\n this.config.onfilecomplete = callback;\n }\n\n set onuploadcomplete ( callback: ( ( files: UploadedFile[] ) => void ) | undefined ) {\n this.config.onuploadcomplete = callback;\n }\n\n /**\n * Formats bytes to human readable string\n */\n private formatBytes ( bytes: number ): string {\n if ( bytes === 0 ) return '0 Bytes';\n const k = 1024;\n const sizes = [ 'Bytes', 'KB', 'MB', 'GB' ];\n const i = Math.floor( Math.log( bytes ) / Math.log( k ) );\n return Math.round( ( bytes / Math.pow( k, i ) ) * 100 ) / 100 + ' ' + sizes[ i ];\n }\n\n /**\n * Generates a unique ID for a file\n */\n private generateFileId (): string {\n return `file-${ Date.now() }-${ Math.random().toString( 36 ).substr( 2, 9 ) }`;\n }\n\n /**\n * Validates a file based on config\n */\n private validateFile ( file: File ): { valid: boolean; error?: string } {\n // Check file size\n const maxSizeBytes = this.maxFileSize * 1024 * 1024;\n if ( file.size > maxSizeBytes ) {\n return {\n valid: false,\n error: `File size exceeds maximum of ${ this.formatBytes( maxSizeBytes ) }`\n };\n }\n\n // Check file type\n if ( this.config.validFiletypes && this.config.validFiletypes.length > 0 ) {\n const extension = file.name.split( '.' ).pop()?.toLowerCase();\n if ( ! extension || ! this.config.validFiletypes.includes( extension ) ) {\n return {\n valid: false,\n error: `File type .${ extension } is not allowed. Valid types: ${ this.config.validFiletypes.join( ', ' ) }`\n };\n }\n }\n\n return { valid: true };\n }\n\n /**\n * Generates a preview for image files\n */\n private async generatePreview ( file: File ): Promise<string | undefined> {\n if ( ! file.type.startsWith( 'image/' ) ) return undefined;\n\n return new Promise( ( resolve ) => {\n const reader = new FileReader();\n reader.onload = ( e ) => resolve( e.target?.result as string );\n reader.onerror = () => resolve( undefined );\n reader.readAsDataURL( file );\n } );\n }\n\n /**\n * Adds files to the upload queue\n */\n private async addFiles ( fileList: FileList ): Promise<void> {\n for ( let i = 0; i < fileList.length; i++ ) {\n const file = fileList[ i ];\n const validation = this.validateFile( file );\n\n const id = this.generateFileId();\n const uploadedFile: UploadedFile = {\n id,\n file,\n status: validation.valid ? 'pending' : 'error',\n progress: 0,\n uploadedBytes: 0,\n error: validation.error\n };\n\n // Generate preview for images\n if ( validation.valid && file.type.startsWith( 'image/' ) ) {\n uploadedFile.preview = await this.generatePreview( file );\n }\n\n this.files.set( id, uploadedFile );\n }\n\n this.renderFileCards();\n }\n\n /**\n * Removes a file from the queue\n */\n private removeFile ( id: string ): void {\n this.files.delete( id );\n this.renderFileCards();\n }\n\n /**\n * Uploads a single file with chunking\n */\n private async uploadFile ( uploadedFile: UploadedFile ): Promise<void> {\n if ( ! this.config.serverURL ) {\n throw new Error( 'Server URL is not configured' );\n }\n\n const { file } = uploadedFile;\n uploadedFile.status = 'uploading';\n uploadedFile.progress = 0;\n this.updateFileCard( uploadedFile.id );\n\n try {\n const headers: HeadersInit = {\n 'Content-Type': 'application/json'\n };\n\n if ( this.config.authToken ) {\n headers[ 'Authorization' ] = `Bearer ${ this.config.authToken }`;\n }\n\n // Step 1: Initiate multipart upload\n const initResponse = await fetch( `${ this.config.serverURL }/api/upload/initiate`, {\n method: 'POST',\n headers,\n body: JSON.stringify( {\n fileName: file.name,\n fileType: file.type\n } )\n } );\n\n if ( ! initResponse.ok ) {\n throw new Error( `Failed to initiate upload: ${ await initResponse.text() }` );\n }\n\n const { uploadId, key } = await initResponse.json();\n\n // Store upload metadata for potential abort\n uploadedFile.uploadId = uploadId;\n uploadedFile.key = key;\n\n // Step 2: Upload chunks\n const chunkSizeBytes = this.config.chunkSize * 1024 * 1024;\n const totalParts = Math.ceil( file.size / chunkSizeBytes );\n const parts: Array<{ partNumber: number; etag: string }> = [];\n\n for ( let partNumber = 1; partNumber <= totalParts; partNumber++ ) {\n // Check if upload was aborted\n if ( this.abortController?.signal.aborted ) {\n throw new Error( 'Upload aborted by user' );\n }\n\n const start = ( partNumber - 1 ) * chunkSizeBytes;\n const end = Math.min( start + chunkSizeBytes, file.size );\n const chunk = file.slice( start, end );\n\n // Create headers for this part upload\n const partHeaders: HeadersInit = {\n 'Content-Type': 'application/octet-stream',\n 'X-Upload-Id': uploadId,\n 'X-Key': key,\n 'X-Part-Number': partNumber.toString()\n };\n\n if ( this.config.authToken ) {\n partHeaders[ 'Authorization' ] = `Bearer ${ this.config.authToken }`;\n }\n\n const partResponse = await fetch( `${ this.config.serverURL }/api/upload/part`, {\n method: 'POST',\n headers: partHeaders,\n body: chunk,\n signal: this.abortController?.signal\n } );\n\n if ( ! partResponse.ok ) {\n throw new Error( `Failed to upload part ${ partNumber }` );\n }\n\n const { etag } = await partResponse.json();\n parts.push( { partNumber, etag } );\n\n // Update progress\n uploadedFile.uploadedBytes = end;\n uploadedFile.progress = ( uploadedFile.uploadedBytes / file.size ) * 100;\n this.updateFileCard( uploadedFile.id );\n }\n\n // Step 3: Complete multipart upload\n const completeResponse = await fetch( `${ this.config.serverURL }/api/upload/complete`, {\n method: 'POST',\n headers,\n body: JSON.stringify( {\n uploadId,\n key,\n parts\n } )\n } );\n\n if ( ! completeResponse.ok ) {\n throw new Error( 'Failed to complete upload' );\n }\n\n uploadedFile.status = 'completed';\n uploadedFile.progress = 100;\n this.updateFileCard( uploadedFile.id );\n\n // Dispatch file complete event\n this.dispatchEvent( new CustomEvent( 'filecomplete', {\n detail: uploadedFile,\n bubbles: true,\n composed: true\n } ) );\n\n // Call callback if provided\n if ( this.config.onfilecomplete ) {\n this.config.onfilecomplete( uploadedFile );\n }\n\n } catch ( error ) {\n // Check if this is an abort error (from AbortController)\n const isAbortError = error instanceof Error &&\n ( error.name === 'AbortError' || error.message === 'Upload aborted by user' );\n\n // Only set error status if not aborted\n if ( ! isAbortError ) {\n uploadedFile.status = 'error';\n uploadedFile.error = error instanceof Error ? error.message : 'Unknown error';\n this.updateFileCard( uploadedFile.id );\n }\n throw error;\n }\n }\n\n /**\n * Starts uploading all pending files\n */\n private async startUpload (): Promise<void> {\n if ( this.isUploading ) return;\n\n const pendingFiles = Array.from( this.files.values() ).filter( f => f.status === 'pending' );\n if ( pendingFiles.length === 0 ) return;\n\n this.isUploading = true;\n this.abortController = new AbortController();\n\n const uploadBtn = this.shadowRoot.querySelector( '#uploadBtn' ) as HTMLButtonElement;\n const abortBtn = this.shadowRoot.querySelector( '#abortBtn' ) as HTMLButtonElement;\n\n if ( uploadBtn ) {\n uploadBtn.disabled = true;\n uploadBtn.textContent = 'Uploading...';\n }\n\n if ( abortBtn ) {\n abortBtn.style.display = 'inline-block';\n }\n\n try {\n for ( const file of pendingFiles ) {\n // Check if upload was aborted\n if ( this.abortController.signal.aborted ) {\n break;\n }\n await this.uploadFile( file );\n }\n\n // All uploads complete\n this.dispatchEvent( new CustomEvent( 'uploadcomplete', {\n detail: Array.from( this.files.values() ),\n bubbles: true,\n composed: true\n } ) );\n\n // Call callback if provided\n if ( this.config.onuploadcomplete ) {\n this.config.onuploadcomplete( Array.from( this.files.values() ) );\n }\n\n } catch ( error ) {\n // Only log non-abort errors\n if ( error instanceof Error && error.message !== 'Upload aborted by user' ) {\n console.error( 'Upload error:', error );\n }\n } finally {\n this.isUploading = false;\n if ( uploadBtn ) {\n uploadBtn.disabled = false;\n uploadBtn.textContent = 'Upload Files';\n }\n if ( abortBtn ) {\n abortBtn.style.display = 'none';\n }\n }\n }\n\n /**\n * Aborts all pending and uploading files\n */\n private async abortAllUploads (): Promise<void> {\n // Trigger abort signal to stop ongoing fetch requests\n if ( this.abortController ) {\n this.abortController.abort();\n }\n\n const filesToAbort = Array.from( this.files.values() ).filter(\n f => f.status === 'pending' || f.status === 'uploading'\n );\n\n if ( filesToAbort.length === 0 ) return;\n\n const headers: HeadersInit = {\n 'Content-Type': 'application/json'\n };\n\n if ( this.config.authToken ) {\n headers[ 'Authorization' ] = `Bearer ${ this.config.authToken }`;\n }\n\n for ( const file of filesToAbort ) {\n // If upload was initiated, abort it on the server\n if ( file.uploadId && file.key ) {\n try {\n await fetch( `${ this.config.serverURL }/api/upload/abort`, {\n method: 'POST',\n headers,\n body: JSON.stringify( {\n uploadId: file.uploadId,\n key: file.key\n } )\n } );\n } catch ( error ) {\n console.error( `Failed to abort upload for ${ file.file.name }:`, error );\n }\n }\n\n // Update file status\n file.status = 'aborted';\n file.error = 'Upload aborted by user';\n this.updateFileCard( file.id );\n }\n\n // Dispatch abort event\n this.dispatchEvent( new CustomEvent( 'uploadaborted', {\n detail: filesToAbort,\n bubbles: true,\n composed: true\n } ) );\n\n // Reset upload state\n this.isUploading = false;\n const uploadBtn = this.shadowRoot.querySelector( '#uploadBtn' ) as HTMLButtonElement;\n const abortBtn = this.shadowRoot.querySelector( '#abortBtn' ) as HTMLButtonElement;\n\n if ( uploadBtn ) {\n uploadBtn.disabled = false;\n uploadBtn.textContent = 'Upload Files';\n }\n\n if ( abortBtn ) {\n abortBtn.style.display = 'none';\n }\n }\n\n /**\n * Updates a single file card in the DOM\n */\n private updateFileCard ( fileId: string ): void {\n const file = this.files.get( fileId );\n if ( ! file ) return;\n\n const card = this.shadowRoot.querySelector( `[data-file-id=\"${ fileId }\"]` ) as HTMLElement;\n if ( ! card ) return;\n\n const progressBar = card.querySelector( '.progress-bar' ) as HTMLElement;\n const progressText = card.querySelector( '.progress-text' ) as HTMLElement;\n const statusDiv = card.querySelector( '.status' ) as HTMLElement;\n\n if ( progressBar ) {\n progressBar.style.width = `${ file.progress }%`;\n\n // Color based on status\n if ( file.status === 'completed' ) {\n progressBar.style.backgroundColor = '#22c55e'; // green\n } else if ( file.status === 'error' ) {\n progressBar.style.backgroundColor = '#ef4444'; // red\n } else if ( file.status === 'aborted' ) {\n progressBar.style.backgroundColor = '#f59e0b'; // orange\n } else {\n progressBar.style.backgroundColor = 'var(--color-primary)';\n }\n }\n\n if ( progressText ) {\n progressText.textContent = `${ Math.round( file.progress ) }%`;\n }\n\n if ( statusDiv && file.error ) {\n statusDiv.textContent = file.error;\n statusDiv.style.display = 'block';\n }\n }\n\n /**\n * Renders all file cards\n */\n private renderFileCards (): void {\n const container = this.shadowRoot.querySelector( '#fileCardsContainer' );\n if ( ! container ) return;\n\n if ( this.files.size === 0 ) {\n container.innerHTML = '';\n return;\n }\n\n container.innerHTML = Array.from( this.files.values() ).map( file => `\n <div class=\"file-card\" data-file-id=\"${ file.id }\">\n <button class=\"remove-btn\" data-file-id=\"${ file.id }\">×</button>\n ${ file.preview ? `<div class=\"preview\"><img src=\"${ file.preview }\" alt=\"Preview\"></div>` : '<div class=\"preview no-preview\">📄</div>' }\n <div class=\"file-info\">\n <div class=\"file-name\" title=\"${ file.file.name }\">${ file.file.name }</div>\n <div class=\"file-size\">${ this.formatBytes( file.file.size ) }</div>\n </div>\n <div class=\"progress-container\">\n <div class=\"progress-bar-bg\">\n <div class=\"progress-bar\" style=\"width: ${ file.progress }%; background-color: ${ file.status === 'completed' ? '#22c55e' : file.status === 'error' ? '#ef4444' : 'var(--color-primary)' }\"></div>\n </div>\n <div class=\"progress-text\">${ Math.round( file.progress ) }%</div>\n </div>\n ${ file.error ? `<div class=\"status error\">${ file.error }</div>` : '' }\n </div>\n ` ).join( '' );\n\n // Bind remove button events\n const removeButtons = container.querySelectorAll( '.remove-btn' );\n removeButtons.forEach( btn => {\n btn.addEventListener( 'click', ( e ) => {\n e.stopPropagation();\n const fileId = ( btn as HTMLElement ).dataset.fileId;\n if ( fileId ) this.removeFile( fileId );\n } );\n } );\n\n // Show/hide upload button\n const uploadBtn = this.shadowRoot.querySelector( '#uploadBtn' ) as HTMLButtonElement;\n if ( uploadBtn ) {\n const hasPendingFiles = Array.from( this.files.values() ).some( f => f.status === 'pending' );\n uploadBtn.style.display = hasPendingFiles ? 'block' : 'none';\n }\n }\n\n /**\n * Binds event listeners\n */\n private bindEvents (): void {\n const uploadZone = this.shadowRoot.querySelector( '#uploadZone' ) as HTMLElement;\n const fileInput = this.shadowRoot.querySelector( '#fileInput' ) as HTMLInputElement;\n const browseBtn = this.shadowRoot.querySelector( '#browseBtn' ) as HTMLButtonElement;\n const uploadBtn = this.shadowRoot.querySelector( '#uploadBtn' ) as HTMLButtonElement;\n const abortBtn = this.shadowRoot.querySelector( '#abortBtn' ) as HTMLButtonElement;\n\n // Click to browse\n browseBtn?.addEventListener( 'click', () => fileInput?.click() );\n\n // Drag and drop\n uploadZone?.addEventListener( 'dragover', ( e ) => {\n e.preventDefault();\n uploadZone.classList.add( 'drag-over' );\n } );\n\n uploadZone?.addEventListener( 'dragleave', () => {\n uploadZone.classList.remove( 'drag-over' );\n } );\n\n uploadZone?.addEventListener( 'drop', ( e ) => {\n e.preventDefault();\n uploadZone.classList.remove( 'drag-over' );\n if ( e.dataTransfer?.files ) {\n this.addFiles( e.dataTransfer.files );\n }\n } );\n\n // File input change\n fileInput?.addEventListener( 'change', ( e ) => {\n const files = ( e.target as HTMLInputElement ).files;\n if ( files ) {\n this.addFiles( files );\n // Reset input so same file can be added again\n fileInput.value = '';\n }\n } );\n\n // Upload button\n uploadBtn?.addEventListener( 'click', () => this.startUpload() );\n\n // Abort button\n abortBtn?.addEventListener( 'click', () => this.abortAllUploads() );\n }\n\n /**\n * Renders the component\n */\n private render (): void {\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n }\n\n * {\n box-sizing: border-box;\n }\n\n .container {\n width: 100%;\n }\n\n .upload-zone {\n border: 2px dashed #ccc;\n border-radius: 8px;\n padding: 40px;\n text-align: center;\n cursor: pointer;\n transition: all 0.3s ease;\n background: #fafafa;\n }\n\n .upload-zone:hover {\n border-color: #4CAF50;\n background: #f0f9f0;\n }\n\n .upload-zone.drag-over {\n border-color: #4CAF50;\n background: #e8f5e9;\n }\n\n .upload-zone-content {\n pointer-events: none;\n }\n\n .upload-icon {\n font-size: 48px;\n margin-bottom: 16px;\n }\n\n .upload-text {\n font-size: 18px;\n margin-bottom: 8px;\n color: #333;\n }\n\n .upload-hint {\n font-size: 14px;\n color: #666;\n }\n\n .browse-btn {\n background: #4CAF50;\n color: white;\n border: none;\n padding: 12px 24px;\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n cursor: pointer;\n margin-top: 16px;\n transition: background 0.3s ease;\n pointer-events: auto;\n }\n\n .browse-btn:hover {\n background: #45a049;\n }\n\n input[type=\"file\"] {\n display: none;\n }\n\n .file-cards-container {\n display: flex;\n flex-wrap: wrap;\n gap: 16px;\n margin-top: 24px;\n }\n\n .file-card {\n position: relative;\n width: 200px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n padding: 12px;\n background: white;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n transition: box-shadow 0.3s ease;\n }\n\n .file-card:hover {\n box-shadow: 0 4px 8px rgba(0,0,0,0.15);\n }\n\n .remove-btn {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 50%;\n background: rgba(239, 68, 68, 0.9);\n color: white;\n font-size: 18px;\n line-height: 1;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n transition: background 0.3s ease;\n z-index: 1;\n }\n\n .remove-btn:hover {\n background: rgba(239, 68, 68, 1);\n }\n\n .preview {\n width: 100%;\n height: 120px;\n border-radius: 4px;\n overflow: hidden;\n background: #f5f5f5;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 8px;\n }\n\n .preview img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n }\n\n .preview.no-preview {\n font-size: 48px;\n }\n\n .file-info {\n margin-bottom: 8px;\n }\n\n .file-name {\n font-size: 14px;\n font-weight: 500;\n color: #333;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-bottom: 4px;\n }\n\n .file-size {\n font-size: 12px;\n color: #666;\n }\n\n .progress-container {\n margin-top: 8px;\n }\n\n .progress-bar-bg {\n width: 100%;\n height: 8px;\n background: #e0e0e0;\n border-radius: 4px;\n overflow: hidden;\n margin-bottom: 4px;\n }\n\n .progress-bar {\n height: 100%;\n background: var(--color-primary, #4CAF50);\n transition: width 0.3s ease, background-color 0.3s ease;\n }\n\n .progress-text {\n font-size: 12px;\n color: #666;\n text-align: center;\n }\n\n .status {\n font-size: 12px;\n color: #ef4444;\n margin-top: 4px;\n display: none;\n }\n\n .status.error {\n display: block;\n }\n\n .upload-btn {\n background: #4CAF50;\n color: white;\n border: none;\n padding: 12px 32px;\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n cursor: pointer;\n margin-top: 24px;\n transition: background 0.3s ease;\n display: none;\n }\n\n .upload-btn:hover:not(:disabled) {\n background: #45a049;\n }\n\n .upload-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .abort-btn {\n background: #ef4444;\n color: white;\n border: none;\n padding: 12px 32px;\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n cursor: pointer;\n margin-top: 24px;\n margin-left: 12px;\n transition: background 0.3s ease;\n display: none;\n }\n\n .abort-btn:hover {\n background: #dc2626;\n }\n\n .buttons-container {\n display: flex;\n align-items: center;\n }\n </style>\n\n <div class=\"container\">\n <div class=\"upload-zone\" id=\"uploadZone\">\n <input type=\"file\" id=\"fileInput\" multiple>\n <div class=\"upload-zone-content\">\n <div class=\"upload-icon\">📁</div>\n <div class=\"upload-text\">Drop files here</div>\n <div class=\"upload-hint\">or</div>\n <button class=\"browse-btn\" id=\"browseBtn\">Browse Files</button>\n </div>\n </div>\n\n <div class=\"file-cards-container\" id=\"fileCardsContainer\"></div>\n\n <div class=\"buttons-container\">\n <button class=\"upload-btn\" id=\"uploadBtn\">Upload Files</button>\n <button class=\"abort-btn\" id=\"abortBtn\">Abort Upload</button>\n </div>\n </div>\n `;\n }\n}\n\n/**\n * Defines the custom element if not already defined\n */\nexport const defineChunkUploader = ( tagName = 'liwe3-chunk-uploader' ): void => {\n if ( typeof window !== 'undefined' && ! customElements.get( tagName ) ) {\n customElements.define( tagName, ChunkUploaderElement );\n }\n};\n\n// Auto-register with default tag name\nif ( typeof window !== 'undefined' ) {\n defineChunkUploader();\n}\n"],"names":["ChunkUploaderElement","name","oldValue","newValue","ext","value","callback","bytes","k","sizes","i","file","maxSizeBytes","extension","resolve","reader","e","fileList","validation","id","uploadedFile","headers","initResponse","uploadId","key","chunkSizeBytes","totalParts","parts","partNumber","start","end","chunk","partHeaders","partResponse","etag","error","pendingFiles","f","uploadBtn","abortBtn","filesToAbort","fileId","card","progressBar","progressText","statusDiv","container","btn","hasPendingFiles","uploadZone","fileInput","browseBtn","files","defineChunkUploader","tagName"],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContainerBox Web Component
|
|
3
|
+
* A container that wraps elements and shows a menu on hover
|
|
4
|
+
*/
|
|
5
|
+
import type { PopoverMenuItem } from './PopoverMenu';
|
|
6
|
+
export type MenuPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
7
|
+
export type ContainerBoxConfig = {
|
|
8
|
+
menuPosition?: MenuPosition;
|
|
9
|
+
menuItems?: PopoverMenuItem[];
|
|
10
|
+
alwaysShowMenu?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare class ContainerBoxElement extends HTMLElement {
|
|
13
|
+
shadowRoot: ShadowRoot;
|
|
14
|
+
private menuPosition;
|
|
15
|
+
private menuItems;
|
|
16
|
+
private alwaysShowMenu;
|
|
17
|
+
private popoverMenu;
|
|
18
|
+
private menuButton;
|
|
19
|
+
constructor();
|
|
20
|
+
connectedCallback(): void;
|
|
21
|
+
disconnectedCallback(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Set menu position
|
|
24
|
+
*/
|
|
25
|
+
setMenuPosition(position: MenuPosition): void;
|
|
26
|
+
/**
|
|
27
|
+
* Get menu position
|
|
28
|
+
*/
|
|
29
|
+
getMenuPosition(): MenuPosition;
|
|
30
|
+
/**
|
|
31
|
+
* Set menu items
|
|
32
|
+
*/
|
|
33
|
+
setMenuItems(items: PopoverMenuItem[]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get menu items
|
|
36
|
+
*/
|
|
37
|
+
getMenuItems(): PopoverMenuItem[];
|
|
38
|
+
/**
|
|
39
|
+
* Set whether menu button is always visible
|
|
40
|
+
*/
|
|
41
|
+
setAlwaysShowMenu(value: boolean): void;
|
|
42
|
+
/**
|
|
43
|
+
* Get whether menu button is always visible
|
|
44
|
+
*/
|
|
45
|
+
getAlwaysShowMenu(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Render the component
|
|
48
|
+
*/
|
|
49
|
+
private render;
|
|
50
|
+
/**
|
|
51
|
+
* Create the popover menu
|
|
52
|
+
*/
|
|
53
|
+
private createPopoverMenu;
|
|
54
|
+
/**
|
|
55
|
+
* Update popover menu items
|
|
56
|
+
*/
|
|
57
|
+
private updatePopoverMenu;
|
|
58
|
+
/**
|
|
59
|
+
* Render menu items recursively
|
|
60
|
+
*/
|
|
61
|
+
private renderMenuItems;
|
|
62
|
+
/**
|
|
63
|
+
* Create and show the popover menu
|
|
64
|
+
*/
|
|
65
|
+
private createAndShowPopover;
|
|
66
|
+
/**
|
|
67
|
+
* Attach event listeners to menu items
|
|
68
|
+
*/
|
|
69
|
+
private attachMenuListeners;
|
|
70
|
+
/**
|
|
71
|
+
* Update menu button position
|
|
72
|
+
*/
|
|
73
|
+
private updateMenuButtonPosition;
|
|
74
|
+
/**
|
|
75
|
+
* Update menu button visibility
|
|
76
|
+
*/
|
|
77
|
+
private updateMenuButtonVisibility;
|
|
78
|
+
/**
|
|
79
|
+
* Setup event listeners
|
|
80
|
+
*/
|
|
81
|
+
private setupEventListeners;
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup event listeners
|
|
84
|
+
*/
|
|
85
|
+
private cleanupEventListeners;
|
|
86
|
+
/**
|
|
87
|
+
* Handle menu button click
|
|
88
|
+
*/
|
|
89
|
+
private handleMenuButtonClick;
|
|
90
|
+
/**
|
|
91
|
+
* Show the menu
|
|
92
|
+
*/
|
|
93
|
+
private showMenu;
|
|
94
|
+
/**
|
|
95
|
+
* Hide the menu
|
|
96
|
+
*/
|
|
97
|
+
private hideMenu;
|
|
98
|
+
/**
|
|
99
|
+
* Handle document click to close menu
|
|
100
|
+
*/
|
|
101
|
+
private handleDocumentClick;
|
|
102
|
+
/**
|
|
103
|
+
* Handle content click - pass through to slotted elements
|
|
104
|
+
*/
|
|
105
|
+
private handleContentClick;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Conditionally defines the custom element if in a browser environment.
|
|
109
|
+
*/
|
|
110
|
+
declare const defineContainerBox: (tagName?: string) => void;
|
|
111
|
+
export { defineContainerBox };
|
|
112
|
+
//# sourceMappingURL=ContainerBox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContainerBox.d.ts","sourceRoot":"","sources":["../src/ContainerBox.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GAAG,cAAc,CAAC;AAErF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,qBAAa,mBAAoB,SAAQ,WAAW;IAC1C,UAAU,EAAE,UAAU,CAAC;IAC/B,OAAO,CAAC,YAAY,CAA+B;IACnD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,cAAc,CAAkB;IACxC,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,UAAU,CAA4B;;IAO9C,iBAAiB,IAAI,IAAI;IAKzB,oBAAoB,IAAI,IAAI;IAI5B;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAK7C;;OAEG;IACH,eAAe,IAAI,YAAY;IAI/B;;OAEG;IACH,YAAY,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,IAAI;IAK5C;;OAEG;IACH,YAAY,IAAI,eAAe,EAAE;IAIjC;;OAEG;IACH,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAKvC;;OAEG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;OAEG;IACH,OAAO,CAAC,MAAM;IAyGd;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqBvB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA0E5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAqE3B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAMhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAUlC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAa7B;;OAEG;IACH,OAAO,CAAC,QAAQ;IAwEhB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAOhB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAY3B;AAED;;GAEG;AACH,QAAA,MAAM,kBAAkB,GAAI,gBAA+B,KAAG,IAI7D,CAAC;AAKF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
class m extends HTMLElement {
|
|
2
|
+
constructor() {
|
|
3
|
+
super(), this.menuPosition = "bottom-left", this.menuItems = [], this.alwaysShowMenu = !1, this.popoverMenu = null, this.menuButton = null, this.attachShadow({ mode: "open" });
|
|
4
|
+
}
|
|
5
|
+
connectedCallback() {
|
|
6
|
+
this.render(), this.setupEventListeners();
|
|
7
|
+
}
|
|
8
|
+
disconnectedCallback() {
|
|
9
|
+
this.cleanupEventListeners();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Set menu position
|
|
13
|
+
*/
|
|
14
|
+
setMenuPosition(e) {
|
|
15
|
+
this.menuPosition = e, this.updateMenuButtonPosition();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get menu position
|
|
19
|
+
*/
|
|
20
|
+
getMenuPosition() {
|
|
21
|
+
return this.menuPosition;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Set menu items
|
|
25
|
+
*/
|
|
26
|
+
setMenuItems(e) {
|
|
27
|
+
this.menuItems = e, this.updatePopoverMenu();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get menu items
|
|
31
|
+
*/
|
|
32
|
+
getMenuItems() {
|
|
33
|
+
return this.menuItems;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Set whether menu button is always visible
|
|
37
|
+
*/
|
|
38
|
+
setAlwaysShowMenu(e) {
|
|
39
|
+
this.alwaysShowMenu = e, this.updateMenuButtonVisibility();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get whether menu button is always visible
|
|
43
|
+
*/
|
|
44
|
+
getAlwaysShowMenu() {
|
|
45
|
+
return this.alwaysShowMenu;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Render the component
|
|
49
|
+
*/
|
|
50
|
+
render() {
|
|
51
|
+
this.shadowRoot.innerHTML = `
|
|
52
|
+
<style>
|
|
53
|
+
:host {
|
|
54
|
+
display: block;
|
|
55
|
+
position: relative;
|
|
56
|
+
font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.container-box {
|
|
60
|
+
position: relative;
|
|
61
|
+
width: 100%;
|
|
62
|
+
height: 100%;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.content-wrapper {
|
|
66
|
+
width: 100%;
|
|
67
|
+
height: 100%;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.menu-button {
|
|
71
|
+
position: absolute;
|
|
72
|
+
background: var(--container-box-menu-bg, #fff);
|
|
73
|
+
border: 1px solid var(--container-box-menu-border, #ddd);
|
|
74
|
+
border-radius: var(--container-box-menu-radius, 4px);
|
|
75
|
+
padding: var(--container-box-menu-padding, 6px 10px);
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
box-shadow: var(--container-box-menu-shadow, 0 2px 8px rgba(0,0,0,0.15));
|
|
78
|
+
z-index: 10;
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transition: opacity 0.2s;
|
|
81
|
+
display: flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
gap: 4px;
|
|
84
|
+
font-size: 14px;
|
|
85
|
+
color: var(--container-box-menu-color, #333);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.menu-button:hover {
|
|
89
|
+
background: var(--container-box-menu-hover-bg, #f5f5f5);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.container-box:hover .menu-button {
|
|
93
|
+
opacity: 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.menu-button.always-visible {
|
|
97
|
+
opacity: 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.menu-button-icon {
|
|
101
|
+
width: 16px;
|
|
102
|
+
height: 16px;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Position variants */
|
|
109
|
+
.menu-button.top-left {
|
|
110
|
+
top: var(--container-box-menu-offset, 8px);
|
|
111
|
+
left: var(--container-box-menu-offset, 8px);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.menu-button.top-right {
|
|
115
|
+
top: var(--container-box-menu-offset, 8px);
|
|
116
|
+
right: var(--container-box-menu-offset, 8px);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.menu-button.bottom-left {
|
|
120
|
+
bottom: var(--container-box-menu-offset, 8px);
|
|
121
|
+
left: var(--container-box-menu-offset, 8px);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.menu-button.bottom-right {
|
|
125
|
+
bottom: var(--container-box-menu-offset, 8px);
|
|
126
|
+
right: var(--container-box-menu-offset, 8px);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.popover-container {
|
|
130
|
+
position: fixed;
|
|
131
|
+
z-index: 10000;
|
|
132
|
+
pointer-events: none;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.popover-container > * {
|
|
136
|
+
pointer-events: auto;
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
139
|
+
|
|
140
|
+
<div class="container-box">
|
|
141
|
+
<div class="content-wrapper">
|
|
142
|
+
<slot></slot>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="menu-button ${this.menuPosition} ${this.alwaysShowMenu ? "always-visible" : ""}">
|
|
145
|
+
<span class="menu-button-icon">⋮</span>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="popover-container"></div>
|
|
149
|
+
`, this.menuButton = this.shadowRoot.querySelector(".menu-button"), this.createPopoverMenu();
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create the popover menu
|
|
153
|
+
*/
|
|
154
|
+
createPopoverMenu() {
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Update popover menu items
|
|
158
|
+
*/
|
|
159
|
+
updatePopoverMenu() {
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Render menu items recursively
|
|
163
|
+
*/
|
|
164
|
+
renderMenuItems(e) {
|
|
165
|
+
return e.map((t) => {
|
|
166
|
+
if (t.label === "---sep")
|
|
167
|
+
return '<div class="menu-separator"></div>';
|
|
168
|
+
const o = t.items && t.items.length > 0;
|
|
169
|
+
return `
|
|
170
|
+
<div class="menu-item ${t.enabled === !1 ? "disabled" : ""} ${o ? "has-submenu" : ""}" data-has-submenu="${o}">
|
|
171
|
+
<span class="menu-item-label">${t.label}</span>
|
|
172
|
+
${o ? '<span class="menu-item-arrow">▶</span>' : ""}
|
|
173
|
+
${o ? `<div class="submenu">${this.renderMenuItems(t.items)}</div>` : ""}
|
|
174
|
+
</div>
|
|
175
|
+
`;
|
|
176
|
+
}).join("");
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create and show the popover menu
|
|
180
|
+
*/
|
|
181
|
+
createAndShowPopover() {
|
|
182
|
+
const e = document.createElement("div");
|
|
183
|
+
return e.className = "custom-popover-menu", e.innerHTML = `
|
|
184
|
+
<style>
|
|
185
|
+
.custom-popover-menu {
|
|
186
|
+
position: fixed;
|
|
187
|
+
background: white;
|
|
188
|
+
border: 1px solid #ccc;
|
|
189
|
+
border-radius: 6px;
|
|
190
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
191
|
+
min-width: 180px;
|
|
192
|
+
z-index: 10000;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.menu-item {
|
|
196
|
+
padding: 10px 16px;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
display: flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
justify-content: space-between;
|
|
201
|
+
transition: background-color 0.2s;
|
|
202
|
+
position: relative;
|
|
203
|
+
font-size: 14px;
|
|
204
|
+
color: #333;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.menu-item:hover {
|
|
208
|
+
background: #f5f5f5;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.menu-item.disabled {
|
|
212
|
+
opacity: 0.5;
|
|
213
|
+
cursor: not-allowed;
|
|
214
|
+
pointer-events: none;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.menu-item.has-submenu:hover .submenu {
|
|
218
|
+
display: block;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.menu-item-label {
|
|
222
|
+
flex: 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.menu-item-arrow {
|
|
226
|
+
margin-left: 12px;
|
|
227
|
+
font-size: 10px;
|
|
228
|
+
color: #666;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.menu-separator {
|
|
232
|
+
height: 1px;
|
|
233
|
+
background: #e0e0e0;
|
|
234
|
+
margin: 4px 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.submenu {
|
|
238
|
+
display: none;
|
|
239
|
+
position: fixed;
|
|
240
|
+
background: white;
|
|
241
|
+
border: 1px solid #ccc;
|
|
242
|
+
border-radius: 6px;
|
|
243
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
244
|
+
min-width: 180px;
|
|
245
|
+
z-index: 10001;
|
|
246
|
+
}
|
|
247
|
+
</style>
|
|
248
|
+
${this.renderMenuItems(this.menuItems)}
|
|
249
|
+
`, e;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Attach event listeners to menu items
|
|
253
|
+
*/
|
|
254
|
+
attachMenuListeners(e, t) {
|
|
255
|
+
e.querySelectorAll(".menu-item:not(.has-submenu)").forEach((r, a) => {
|
|
256
|
+
let l = 0;
|
|
257
|
+
const s = ((u) => {
|
|
258
|
+
for (const c of u)
|
|
259
|
+
if (c.label !== "---sep" && !(c.items && c.items.length > 0)) {
|
|
260
|
+
if (l === a) return c;
|
|
261
|
+
l++;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
})(t);
|
|
265
|
+
s && s.onclick && r.addEventListener("click", (u) => {
|
|
266
|
+
u.stopPropagation(), s.onclick(), this.hideMenu();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
const n = this.getBoundingClientRect();
|
|
270
|
+
e.querySelectorAll(".menu-item.has-submenu").forEach((r) => {
|
|
271
|
+
r.addEventListener("mouseenter", () => {
|
|
272
|
+
const a = r.querySelector(".submenu");
|
|
273
|
+
if (a) {
|
|
274
|
+
const l = r.getBoundingClientRect(), p = a.getBoundingClientRect();
|
|
275
|
+
let s = l.right + 5, u = l.top;
|
|
276
|
+
s + p.width > n.right && (s = l.left - p.width - 5, s < n.left && (s = n.left)), u + p.height > n.bottom && (u = n.bottom - p.height, u < n.top && (u = n.top)), a.style.left = `${s}px`, a.style.top = `${u}px`;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Update menu button position
|
|
283
|
+
*/
|
|
284
|
+
updateMenuButtonPosition() {
|
|
285
|
+
this.menuButton && (this.menuButton.className = `menu-button ${this.menuPosition} ${this.alwaysShowMenu ? "always-visible" : ""}`);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Update menu button visibility
|
|
289
|
+
*/
|
|
290
|
+
updateMenuButtonVisibility() {
|
|
291
|
+
this.menuButton && (this.alwaysShowMenu ? this.menuButton.classList.add("always-visible") : this.menuButton.classList.remove("always-visible"));
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Setup event listeners
|
|
295
|
+
*/
|
|
296
|
+
setupEventListeners() {
|
|
297
|
+
this.menuButton && this.menuButton.addEventListener("click", this.handleMenuButtonClick.bind(this));
|
|
298
|
+
const e = this.shadowRoot.querySelector(".content-wrapper");
|
|
299
|
+
e && e.addEventListener("click", this.handleContentClick.bind(this));
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Cleanup event listeners
|
|
303
|
+
*/
|
|
304
|
+
cleanupEventListeners() {
|
|
305
|
+
this.menuButton && this.menuButton.removeEventListener("click", this.handleMenuButtonClick.bind(this));
|
|
306
|
+
const e = this.shadowRoot.querySelector(".content-wrapper");
|
|
307
|
+
e && e.removeEventListener("click", this.handleContentClick.bind(this));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Handle menu button click
|
|
311
|
+
*/
|
|
312
|
+
handleMenuButtonClick(e) {
|
|
313
|
+
e.stopPropagation(), this.menuButton && (this.popoverMenu ? this.hideMenu() : this.showMenu());
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Show the menu
|
|
317
|
+
*/
|
|
318
|
+
showMenu() {
|
|
319
|
+
if (!this.menuButton || this.menuItems.length === 0) return;
|
|
320
|
+
this.popoverMenu && this.popoverMenu.remove(), this.popoverMenu = this.createAndShowPopover(), document.body.appendChild(this.popoverMenu);
|
|
321
|
+
const e = this.menuButton.getBoundingClientRect(), t = this.getBoundingClientRect();
|
|
322
|
+
this.popoverMenu.style.left = `${e.left}px`, this.popoverMenu.style.top = `${e.bottom + 4}px`, requestAnimationFrame(() => {
|
|
323
|
+
const o = this.popoverMenu.getBoundingClientRect();
|
|
324
|
+
let n = e.left, i = e.bottom + 4;
|
|
325
|
+
n + o.width > t.right && (n = t.right - o.width, n < t.left && (n = t.left)), n < t.left && (n = t.left), i + o.height > t.bottom && (i = e.top - o.height - 4, i < t.top && (i = t.top)), i < t.top && (i = t.top), this.popoverMenu.style.left = `${n}px`, this.popoverMenu.style.top = `${i}px`;
|
|
326
|
+
}), this.attachMenuListeners(this.popoverMenu, this.menuItems), setTimeout(() => {
|
|
327
|
+
document.addEventListener("click", this.handleDocumentClick.bind(this), { once: !0 });
|
|
328
|
+
}, 0);
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Hide the menu
|
|
332
|
+
*/
|
|
333
|
+
hideMenu() {
|
|
334
|
+
this.popoverMenu && (this.popoverMenu.remove(), this.popoverMenu = null);
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Handle document click to close menu
|
|
338
|
+
*/
|
|
339
|
+
handleDocumentClick(e) {
|
|
340
|
+
const t = e.target, o = this.popoverMenu;
|
|
341
|
+
o && !o.contains(t) && !this.menuButton?.contains(t) && this.hideMenu();
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Handle content click - pass through to slotted elements
|
|
345
|
+
*/
|
|
346
|
+
handleContentClick(e) {
|
|
347
|
+
const t = this.shadowRoot.querySelector("slot");
|
|
348
|
+
!t || t.assignedElements().length === 0 || e.stopPropagation();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const d = (h = "liwe3-container-box") => {
|
|
352
|
+
typeof window < "u" && !window.customElements.get(h) && customElements.define(h, m);
|
|
353
|
+
};
|
|
354
|
+
d();
|
|
355
|
+
export {
|
|
356
|
+
m as ContainerBoxElement,
|
|
357
|
+
d as defineContainerBox
|
|
358
|
+
};
|
|
359
|
+
//# sourceMappingURL=ContainerBox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContainerBox.js","sources":["../src/ContainerBox.ts"],"sourcesContent":["/**\n * ContainerBox Web Component\n * A container that wraps elements and shows a menu on hover\n */\n\nimport type { PopoverMenuItem } from './PopoverMenu';\n\nexport type MenuPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';\n\nexport type ContainerBoxConfig = {\n menuPosition?: MenuPosition;\n menuItems?: PopoverMenuItem[];\n alwaysShowMenu?: boolean;\n};\n\nexport class ContainerBoxElement extends HTMLElement {\n declare shadowRoot: ShadowRoot;\n private menuPosition: MenuPosition = 'bottom-left';\n private menuItems: PopoverMenuItem[] = [];\n private alwaysShowMenu: boolean = false;\n private popoverMenu: HTMLElement | null = null;\n private menuButton: HTMLElement | null = null;\n\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n\n connectedCallback(): void {\n this.render();\n this.setupEventListeners();\n }\n\n disconnectedCallback(): void {\n this.cleanupEventListeners();\n }\n\n /**\n * Set menu position\n */\n setMenuPosition(position: MenuPosition): void {\n this.menuPosition = position;\n this.updateMenuButtonPosition();\n }\n\n /**\n * Get menu position\n */\n getMenuPosition(): MenuPosition {\n return this.menuPosition;\n }\n\n /**\n * Set menu items\n */\n setMenuItems(items: PopoverMenuItem[]): void {\n this.menuItems = items;\n this.updatePopoverMenu();\n }\n\n /**\n * Get menu items\n */\n getMenuItems(): PopoverMenuItem[] {\n return this.menuItems;\n }\n\n /**\n * Set whether menu button is always visible\n */\n setAlwaysShowMenu(value: boolean): void {\n this.alwaysShowMenu = value;\n this.updateMenuButtonVisibility();\n }\n\n /**\n * Get whether menu button is always visible\n */\n getAlwaysShowMenu(): boolean {\n return this.alwaysShowMenu;\n }\n\n /**\n * Render the component\n */\n private render(): void {\n this.shadowRoot.innerHTML = `\n <style>\n :host {\n display: block;\n position: relative;\n font-family: var(--font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);\n }\n\n .container-box {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n .content-wrapper {\n width: 100%;\n height: 100%;\n }\n\n .menu-button {\n position: absolute;\n background: var(--container-box-menu-bg, #fff);\n border: 1px solid var(--container-box-menu-border, #ddd);\n border-radius: var(--container-box-menu-radius, 4px);\n padding: var(--container-box-menu-padding, 6px 10px);\n cursor: pointer;\n box-shadow: var(--container-box-menu-shadow, 0 2px 8px rgba(0,0,0,0.15));\n z-index: 10;\n opacity: 0;\n transition: opacity 0.2s;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 14px;\n color: var(--container-box-menu-color, #333);\n }\n\n .menu-button:hover {\n background: var(--container-box-menu-hover-bg, #f5f5f5);\n }\n\n .container-box:hover .menu-button {\n opacity: 1;\n }\n\n .menu-button.always-visible {\n opacity: 1;\n }\n\n .menu-button-icon {\n width: 16px;\n height: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n\n /* Position variants */\n .menu-button.top-left {\n top: var(--container-box-menu-offset, 8px);\n left: var(--container-box-menu-offset, 8px);\n }\n\n .menu-button.top-right {\n top: var(--container-box-menu-offset, 8px);\n right: var(--container-box-menu-offset, 8px);\n }\n\n .menu-button.bottom-left {\n bottom: var(--container-box-menu-offset, 8px);\n left: var(--container-box-menu-offset, 8px);\n }\n\n .menu-button.bottom-right {\n bottom: var(--container-box-menu-offset, 8px);\n right: var(--container-box-menu-offset, 8px);\n }\n\n .popover-container {\n position: fixed;\n z-index: 10000;\n pointer-events: none;\n }\n\n .popover-container > * {\n pointer-events: auto;\n }\n </style>\n\n <div class=\"container-box\">\n <div class=\"content-wrapper\">\n <slot></slot>\n </div>\n <div class=\"menu-button ${this.menuPosition} ${this.alwaysShowMenu ? 'always-visible' : ''}\">\n <span class=\"menu-button-icon\">⋮</span>\n </div>\n </div>\n <div class=\"popover-container\"></div>\n `;\n\n this.menuButton = this.shadowRoot.querySelector('.menu-button');\n this.createPopoverMenu();\n }\n\n /**\n * Create the popover menu\n */\n private createPopoverMenu(): void {\n // We'll create the popover dynamically when needed\n }\n\n /**\n * Update popover menu items\n */\n private updatePopoverMenu(): void {\n // Menu items are stored and used when showing the menu\n }\n\n /**\n * Render menu items recursively\n */\n private renderMenuItems(items: PopoverMenuItem[]): string {\n return items.map(item => {\n if (item.label === '---sep') {\n return '<div class=\"menu-separator\"></div>';\n }\n\n const hasSubmenu = item.items && item.items.length > 0;\n const isDisabled = item.enabled === false;\n const disabledClass = isDisabled ? 'disabled' : '';\n const submenuClass = hasSubmenu ? 'has-submenu' : '';\n\n return `\n <div class=\"menu-item ${disabledClass} ${submenuClass}\" data-has-submenu=\"${hasSubmenu}\">\n <span class=\"menu-item-label\">${item.label}</span>\n ${hasSubmenu ? '<span class=\"menu-item-arrow\">▶</span>' : ''}\n ${hasSubmenu ? `<div class=\"submenu\">${this.renderMenuItems(item.items!)}</div>` : ''}\n </div>\n `;\n }).join('');\n }\n\n /**\n * Create and show the popover menu\n */\n private createAndShowPopover(): HTMLElement {\n const popover = document.createElement('div');\n popover.className = 'custom-popover-menu';\n popover.innerHTML = `\n <style>\n .custom-popover-menu {\n position: fixed;\n background: white;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n min-width: 180px;\n z-index: 10000;\n }\n\n .menu-item {\n padding: 10px 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: space-between;\n transition: background-color 0.2s;\n position: relative;\n font-size: 14px;\n color: #333;\n }\n\n .menu-item:hover {\n background: #f5f5f5;\n }\n\n .menu-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .menu-item.has-submenu:hover .submenu {\n display: block;\n }\n\n .menu-item-label {\n flex: 1;\n }\n\n .menu-item-arrow {\n margin-left: 12px;\n font-size: 10px;\n color: #666;\n }\n\n .menu-separator {\n height: 1px;\n background: #e0e0e0;\n margin: 4px 8px;\n }\n\n .submenu {\n display: none;\n position: fixed;\n background: white;\n border: 1px solid #ccc;\n border-radius: 6px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n min-width: 180px;\n z-index: 10001;\n }\n </style>\n ${this.renderMenuItems(this.menuItems)}\n `;\n\n return popover;\n }\n\n /**\n * Attach event listeners to menu items\n */\n private attachMenuListeners(popover: HTMLElement, items: PopoverMenuItem[]): void {\n const menuItems = popover.querySelectorAll('.menu-item:not(.has-submenu)');\n \n menuItems.forEach((element, index) => {\n let flatIndex = 0;\n const findItem = (items: PopoverMenuItem[]): PopoverMenuItem | null => {\n for (const item of items) {\n if (item.label === '---sep') continue;\n if (!(item.items && item.items.length > 0)) {\n if (flatIndex === index) return item;\n flatIndex++;\n }\n }\n return null;\n };\n\n const item = findItem(items);\n if (item && item.onclick) {\n element.addEventListener('click', (e) => {\n e.stopPropagation();\n item.onclick!();\n this.hideMenu();\n });\n }\n });\n\n // Handle submenu positioning within container boundaries\n const containerRect = this.getBoundingClientRect();\n const submenuItems = popover.querySelectorAll('.menu-item.has-submenu');\n \n submenuItems.forEach(element => {\n element.addEventListener('mouseenter', () => {\n const submenu = element.querySelector('.submenu') as HTMLElement;\n if (submenu) {\n const rect = element.getBoundingClientRect();\n const submenuRect = submenu.getBoundingClientRect();\n \n let left = rect.right + 5;\n let top = rect.top;\n \n // Check if submenu would overflow container on the right\n if (left + submenuRect.width > containerRect.right) {\n // Try positioning to the left of the parent item\n left = rect.left - submenuRect.width - 5;\n \n // If still overflowing left, position at container left edge\n if (left < containerRect.left) {\n left = containerRect.left;\n }\n }\n \n // Check if submenu would overflow container at the bottom\n if (top + submenuRect.height > containerRect.bottom) {\n // Align bottom of submenu with bottom of container\n top = containerRect.bottom - submenuRect.height;\n \n // If still overflowing at top, position at container top\n if (top < containerRect.top) {\n top = containerRect.top;\n }\n }\n \n submenu.style.left = `${left}px`;\n submenu.style.top = `${top}px`;\n }\n });\n });\n }\n\n /**\n * Update menu button position\n */\n private updateMenuButtonPosition(): void {\n if (!this.menuButton) return;\n\n this.menuButton.className = `menu-button ${this.menuPosition} ${this.alwaysShowMenu ? 'always-visible' : ''}`;\n }\n\n /**\n * Update menu button visibility\n */\n private updateMenuButtonVisibility(): void {\n if (!this.menuButton) return;\n\n if (this.alwaysShowMenu) {\n this.menuButton.classList.add('always-visible');\n } else {\n this.menuButton.classList.remove('always-visible');\n }\n }\n\n /**\n * Setup event listeners\n */\n private setupEventListeners(): void {\n if (this.menuButton) {\n this.menuButton.addEventListener('click', this.handleMenuButtonClick.bind(this));\n }\n\n // Allow clicks to pass through to slotted content\n const contentWrapper = this.shadowRoot.querySelector('.content-wrapper');\n if (contentWrapper) {\n contentWrapper.addEventListener('click', this.handleContentClick.bind(this));\n }\n }\n\n /**\n * Cleanup event listeners\n */\n private cleanupEventListeners(): void {\n if (this.menuButton) {\n this.menuButton.removeEventListener('click', this.handleMenuButtonClick.bind(this));\n }\n\n const contentWrapper = this.shadowRoot.querySelector('.content-wrapper');\n if (contentWrapper) {\n contentWrapper.removeEventListener('click', this.handleContentClick.bind(this));\n }\n }\n\n /**\n * Handle menu button click\n */\n private handleMenuButtonClick(e: Event): void {\n e.stopPropagation();\n\n if (!this.menuButton) return;\n\n // Toggle menu visibility\n if (this.popoverMenu) {\n this.hideMenu();\n } else {\n this.showMenu();\n }\n }\n\n /**\n * Show the menu\n */\n private showMenu(): void {\n if (!this.menuButton || this.menuItems.length === 0) return;\n\n // Create the popover\n if (this.popoverMenu) {\n this.popoverMenu.remove();\n }\n\n this.popoverMenu = this.createAndShowPopover() as any;\n document.body.appendChild(this.popoverMenu as any);\n\n // Get button and container bounds\n const buttonRect = this.menuButton.getBoundingClientRect();\n const containerRect = this.getBoundingClientRect();\n \n // Temporarily position to measure\n (this.popoverMenu as any).style.left = `${buttonRect.left}px`;\n (this.popoverMenu as any).style.top = `${buttonRect.bottom + 4}px`;\n \n // Wait for next frame to get accurate measurements\n requestAnimationFrame(() => {\n const menuRect = (this.popoverMenu as any).getBoundingClientRect();\n \n let left = buttonRect.left;\n let top = buttonRect.bottom + 4;\n \n // Check if menu would overflow container on the right\n if (left + menuRect.width > containerRect.right) {\n // Try to align right edge of menu with right edge of container\n left = containerRect.right - menuRect.width;\n \n // If still overflowing left, align with left edge of container\n if (left < containerRect.left) {\n left = containerRect.left;\n }\n }\n \n // Check if menu would overflow container on the left\n if (left < containerRect.left) {\n left = containerRect.left;\n }\n \n // Check if menu would overflow container at the bottom\n if (top + menuRect.height > containerRect.bottom) {\n // Try positioning above the button\n top = buttonRect.top - menuRect.height - 4;\n \n // If still overflowing at top, position at container top\n if (top < containerRect.top) {\n top = containerRect.top;\n }\n }\n \n // Check if menu would overflow container at the top\n if (top < containerRect.top) {\n top = containerRect.top;\n }\n \n // Apply final position\n (this.popoverMenu as any).style.left = `${left}px`;\n (this.popoverMenu as any).style.top = `${top}px`;\n });\n\n // Attach event listeners\n this.attachMenuListeners(this.popoverMenu as any, this.menuItems);\n\n // Add click listener to close menu when clicking outside\n setTimeout(() => {\n document.addEventListener('click', this.handleDocumentClick.bind(this), { once: true });\n }, 0);\n }\n\n /**\n * Hide the menu\n */\n private hideMenu(): void {\n if (this.popoverMenu) {\n (this.popoverMenu as any).remove();\n this.popoverMenu = null;\n }\n }\n\n /**\n * Handle document click to close menu\n */\n private handleDocumentClick(e: Event): void {\n const target = e.target as Node;\n const popoverElement = this.popoverMenu as any;\n if (popoverElement && !popoverElement.contains(target) && !this.menuButton?.contains(target)) {\n this.hideMenu();\n }\n }\n\n /**\n * Handle content click - pass through to slotted elements\n */\n private handleContentClick(e: Event): void {\n // Get the slotted elements\n const slot = this.shadowRoot.querySelector('slot');\n if (!slot) return;\n\n const assignedElements = slot.assignedElements();\n if (assignedElements.length === 0) return;\n\n // The click naturally propagates to slotted content\n // We just need to ensure it doesn't trigger the menu\n e.stopPropagation();\n }\n}\n\n/**\n * Conditionally defines the custom element if in a browser environment.\n */\nconst defineContainerBox = (tagName = 'liwe3-container-box'): void => {\n if (typeof window !== 'undefined' && !window.customElements.get(tagName)) {\n customElements.define(tagName, ContainerBoxElement);\n }\n};\n\n// Auto-register with default tag name\ndefineContainerBox();\n\nexport { defineContainerBox };\n"],"names":["ContainerBoxElement","position","items","value","item","hasSubmenu","popover","element","index","flatIndex","e","containerRect","submenu","rect","submenuRect","left","top","contentWrapper","buttonRect","menuRect","target","popoverElement","slot","defineContainerBox","tagName"],"mappings":"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"}
|