diff --git a/flottform/forms/.gitignore b/flottform/forms/.gitignore new file mode 100644 index 0000000..c4f456b --- /dev/null +++ b/flottform/forms/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example +.vercel +.output +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +/test-results +/docs diff --git a/flottform/forms/package.json b/flottform/forms/package.json index 5d904a8..335c01a 100644 --- a/flottform/forms/package.json +++ b/flottform/forms/package.json @@ -39,15 +39,19 @@ "lint": "prettier --check . && eslint .", "format": "prettier --write .", "test": "pnpm run test:unit", - "test:unit": "vitest" + "test:unit": "vitest", + "docs": "typedoc --options typedoc.json" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.12.0", + "@fontsource/roboto": "^5.1.0", "@types/qrcode": "^1.5.5", "@vitest/browser": "^2.1.2", "globals": "^15.10.0", "qrcode": "^1.5.4", + "typedoc": "^0.26.10", + "typedoc-material-theme": "^1.1.0", "vite": "^5.4.8", "vite-plugin-dts": "^4.2.3", "vitest": "^2.1.2", diff --git a/flottform/forms/src/default-component.ts b/flottform/forms/src/default-component.ts index edf3111..528d1f5 100644 --- a/flottform/forms/src/default-component.ts +++ b/flottform/forms/src/default-component.ts @@ -3,6 +3,54 @@ import { FlottformTextInputHost } from './flottform-text-input-host'; import { BaseInputHost, BaseListeners } from './internal'; import { FlottformCreateFileParams, FlottformCreateTextParams } from './types'; +/** + * The result object returned when creating a Flottform component. + * Contains the root element and methods to interact with the component. + */ +export type DefaultFlottformComponentResult = { + /** The root HTML element of the Flottform component */ + flottformRoot: HTMLElement; + /** + * Retrieves all existing Flottform items (both file and text entries) in the UI. + * + * @returns {NodeListOf | null} - A NodeList of Flottform input items (file and text entries), or `null` if no items are found. + */ + getAllFlottformItems: () => NodeListOf | null; + /** + * Creates a UI entry for receiving a file via WebRTC. + * + * @param {Object} params - Configuration options for the file input entry. + * @param {string} params.flottformApi - URL of the WebRTC signaling server. + * @param {Function} params.createClientUrl - A function that returns the URL where the second peer can upload the file. + * @param {HTMLInputElement} params.inputField - The file input field in the main form where the received file will be displayed or processed. + * @param {string} [params.id] - Optional ID for the file input entry. + * @param {string} [params.additionalItemClasses] - Optional additional CSS classes for styling the file input entry. + * @param {string} [params.label] - Optional label text for the file input entry. + * @param {string} [params.buttonLabel] - Optional text for the button that triggers the file reception. + * @param {string | Function} [params.onErrorText] - Optional error message displayed if the file transfer fails. + * @param {string} [params.onSuccessText] - Optional success message displayed after the file is successfully received. + * + * @returns {void} + */ + createFileItem: (params: FlottformCreateFileParams) => void; + /** + * Creates a UI entry for receiving text via WebRTC. + * + * @param {Object} params - Configuration options for the text input entry. + * @param {string} params.flottformApi - URL of the WebRTC signaling server. + * @param {Function} params.createClientUrl - A function that returns the URL where the second peer can send the text. + * @param {string} [params.id] - Optional ID for the text input entry. + * @param {string} [params.additionalItemClasses] - Optional additional CSS classes for styling the text input entry. + * @param {string} [params.label] - Optional label text for the text input entry. + * @param {string} [params.buttonLabel] - Optional text for the button that triggers the text reception. + * @param {string | Function} [params.onErrorText] - Optional error message displayed if the text transfer fails. + * @param {string} [params.onSuccessText] - Optional success message displayed after the text is successfully received. + * + * @returns {void} + */ + createTextItem: (params: FlottformCreateTextParams) => void; +}; + const openInputsList = () => { const flottformElementsContainerWrapper: HTMLDivElement = document.querySelector( '.flottform-elements-container-wrapper' @@ -40,6 +88,62 @@ const createLinkAndQrCode = (qrCode: string, link: string) => { createChannelLinkWithOffer }; }; + +/** + * Creates and attaches a default Flottform UI component to the specified anchor element. This UI acts as an intermediary to facilitate WebRTC-based peer-to-peer connections between two devices, allowing one peer to send data (files or text) to the other. + * + * Developers can customize aspects of the UI, such as the button text, descriptions, and CSS classes, while using the default behavior provided by the function to quickly set up the peer-to-peer data transfer mechanism. + * + * The generated UI component will be attached as a child to `flottformAnchorElement`. The UI includes a dialog with a QR code or link that the second peer can use to connect and upload files/text to the main form. + * + * The dialog also shows the progress of receiving the file or text from the other device or any errors that can happen. + * + * The returned object allows for further interactions, such as adding the UI necessary to handle receiving a file or text, and retrieving all existing Flottform items within the UI. + * + * @param {Object} params - Configuration options for setting up the Flottform component. + * @param {HTMLElement} params.flottformAnchorElement - The HTML element to which the Flottform component will be attached. It determines where the UI will be built on the page. + * @param {HTMLElement} [params.flottformRootElement] - An optional root element to use. If not provided, a new default root element (a `div` with the class `flottform-root`) will be created. + * @param {string} [params.additionalComponentClass] - Optional additional class to add for custom styling of the component. + * @param {string} [params.flottformRootTitle] - Optional title to set for the Flottform root element This is the text displayed on the button that opens the dialog (with the class `flottform-root-opener-button`). + * @param {string} [params.flottformRootDescription] - Optional description text shown inside the dialog when it is opened. It provides context for the user about what the Flottform component does (e.g., "Receive files from other devices"). + * + * @returns {Object} - Returns an object with methods to interact with the Flottform component. + * @returns {HTMLElement} returns.flottformRoot - The root element of the Flottform UI. + * @returns {Function} returns.createFileItem - Function to create an entry in the UI for receiving files from another peer. The entry will show the QR code/link, progress of the file transfer, and handle any errors. + * @returns {Function} returns.createTextItem - Function to create an entry in the UI for receiving text from another peer. Similar to `createFileItem`, it handles text input, progress tracking, and error handling. + * @returns {Function} returns.getAllFlottformItems - Function to retrieve all current Flottform items (file and text entries) in the dialog. + * + * @example + * + * + * const flottformComponent = createDefaultFlottformComponent({ + * flottformAnchorElement: document.getElementById('form-anchor'), + * flottformRootTitle: 'Share Data via Flottform', + * flottformRootDescription: 'This form is powered by Flottform. Upload files or send text from another device using the provided QR code.' + *}); + * + * // Create an entry to receive files + * flottformComponent.createFileItem({ + * flottformApi, // URL of the WebRTC signaling server + * createClientUrl: ({ endpointId }) => `/upload/${endpointId}`, // URL of the client page for file uploads + * inputField: document.querySelector('#fileInput'), // The file input field in the main form + * label: 'Upload Resume', // Label for the file input + * buttonLabel:'Submit File', // Button text for the file input + * onSuccessText: 'File received successfully!' // Success message displayed after the file is received + * }); + * + * // Create an entry to receive text + * flottformComponent.createTextItem({ + * flottformApi, // URL of the WebRTC signaling server + * createClientUrl: ({ endpointId }) => `/text/${endpointId}`, // URL of the client page for sending text + * label: 'Enter your message', // Label for the text input + * buttonLabel: 'Send Message', // Button text for the text input + * onErrorText: (error) => `Failed to receive text: ${error.message}` // Error message if text transfer fails + * }); + * + * // Retrieve all Flottform items in the UI (file and text entries) + * const allItems = flottformComponent.getAllFlottformItems(); + */ export const createDefaultFlottformComponent = ({ flottformAnchorElement, flottformRootElement, @@ -52,12 +156,7 @@ export const createDefaultFlottformComponent = ({ additionalComponentClass?: string; flottformRootTitle?: string; flottformRootDescription?: string; -}): { - flottformRoot: HTMLElement; - createFileItem: (params: FlottformCreateFileParams) => void; - createTextItem: (params: FlottformCreateTextParams) => void; - getAllFlottformItems: () => NodeListOf | null; -} => { +}): DefaultFlottformComponentResult => { const flottformRoot: HTMLElement = flottformRootElement ?? document.querySelector('.flottform-root') ?? diff --git a/flottform/forms/src/flottform-channel-client.ts b/flottform/forms/src/flottform-channel-client.ts index ab9bb93..c775e90 100644 --- a/flottform/forms/src/flottform-channel-client.ts +++ b/flottform/forms/src/flottform-channel-client.ts @@ -21,7 +21,26 @@ type Listeners = { error: [e: string]; bufferedamountlow: []; }; - +/** + * A class used to represent one peer (called client) to establish a WebRTC connection with another peer (called host). + * It handles ICE candidate gathering and sending/receiving data. + * The connection is initiated only when `start` method is called. + * + * This class emits various events during the connection lifecycle, such as `connected`, `disconnected`, and `error`, allowing you to respond to changes in the connection state. + * + * @fires init - Emitted when the client is initialized. + * @fires retrieving-info-from-endpoint - Emitted when information is being retrieved from the endpoint. + * @fires sending-client-info - Emitted when client information is being sent to the host. + * @fires connecting-to-host - Emitted when attempting to connect to the host. + * @fires connected - Emitted when the connection is successfully established. + * @fires connection-impossible - Emitted if the connection to the host cannot be established. + * @fires done - Emitted when the all of the data is received. + * @fires disconnected - Emitted when the connection is closed. + * @fires error - Emitted when there is an error during the connection. + * @fires bufferedamountlow - Emitted when the buffered amount for data channels is low. + * + * @extends EventEmitter + */ export class FlottformChannelClient extends EventEmitter { private flottformApi: string | URL; private endpointId: string; @@ -34,7 +53,16 @@ export class FlottformChannelClient extends EventEmitter { private dataChannel: RTCDataChannel | null = null; private pollForIceTimer: NodeJS.Timeout | number | null = null; private BUFFER_THRESHOLD = 128 * 1024; // 128KB buffer threshold (maximum of 4 chunks in the buffer waiting to be sent over the network) - + /** + * Creates an instance of FlottformChannelClient + * + * @param {Object} config - The configuration for setting up the channel for the host. + * + * @param {endpointId} - The unique identifier of the endpoint to connect to. + * @param {flottformApi} - The API endpoint for retrieving connection information. + * @param {pollTimeForIceInMs} - Optional time in milliseconds for polling ICE candidates. + * @param {logger} - Optional logger for logging connection events (default: `console`). + */ constructor({ endpointId, flottformApi, @@ -61,6 +89,9 @@ export class FlottformChannelClient extends EventEmitter { this.logger.info(`**Client State changed to: ${newState}`, details == undefined ? '' : details); }; + /** + * Starts the WebRTC connection process. The connection is not established until this method is called. + */ start = async () => { if (this.openPeerConnection) { this.close(); @@ -114,6 +145,11 @@ export class FlottformChannelClient extends EventEmitter { this.startPollingForIceCandidates(getEndpointInfoUrl); }; + /** + * Closes the WebRTC connection if it is currently established. + * + * @fires disconnected - Emitted when the connection is successfully closed. + */ close = () => { if (this.openPeerConnection) { this.openPeerConnection.close(); @@ -122,6 +158,12 @@ export class FlottformChannelClient extends EventEmitter { this.changeState('disconnected'); }; + /** + * Sends data to the connected peer via the WebRTC data channel. + * + * @param data - The data to send to the peer. + * @fires error - Emits the state error if the connection is not established. + */ // sendData = (data: string | Blob | ArrayBuffer | ArrayBufferView) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any sendData = (data: any) => { @@ -135,6 +177,11 @@ export class FlottformChannelClient extends EventEmitter { this.dataChannel.send(data); }; + /** + * Determines if more data can be sent based on the WebRTC data channel's buffered amount. This is useful when dealing with large amounts of data. + * + * @returns `true` if more data can be sent, otherwise `false`. + */ canSendMoreData = () => { return ( this.dataChannel && @@ -175,6 +222,7 @@ export class FlottformChannelClient extends EventEmitter { this.logger.error(`onicecandidateerror - ${this.openPeerConnection!.connectionState}`, e); }; }; + private setUpConnectionStateGathering = (getEndpointInfoUrl: string) => { if (this.openPeerConnection === null) { this.changeState( @@ -212,12 +260,14 @@ export class FlottformChannelClient extends EventEmitter { } }; }; + private stopPollingForIceCandidates = async () => { if (this.pollForIceTimer) { clearTimeout(this.pollForIceTimer); } this.pollForIceTimer = null; }; + private startPollingForIceCandidates = async (getEndpointInfoUrl: string) => { if (this.pollForIceTimer) { clearTimeout(this.pollForIceTimer); @@ -227,6 +277,7 @@ export class FlottformChannelClient extends EventEmitter { this.pollForIceTimer = setTimeout(this.startPollingForIceCandidates, this.pollTimeForIceInMs); }; + private pollForConnection = async (getEndpointInfoUrl: string) => { if (this.openPeerConnection === null) { this.changeState('error', "openPeerConnection is null. Unable to retrieve Host's details"); @@ -239,6 +290,7 @@ export class FlottformChannelClient extends EventEmitter { await this.openPeerConnection.addIceCandidate(iceCandidate); } }; + private putClientInfo = async ( putClientInfoUrl: string, clientKey: string, diff --git a/flottform/forms/src/flottform-channel-host.ts b/flottform/forms/src/flottform-channel-host.ts index ca75481..c5e776c 100644 --- a/flottform/forms/src/flottform-channel-host.ts +++ b/flottform/forms/src/flottform-channel-host.ts @@ -8,7 +8,26 @@ import { DEFAULT_WEBRTC_CONFIG, setIncludes } from './internal'; - +/** + * A class used to represent one peer (called client) to establish a WebRTC connection with another peer (called host). + * It handles ICE candidate gathering and sending/receiving data. + * The connection is initiated only when `start` method is called. + * + * This class emits various events throughout the connection lifecycle such as `connected`, `disconnected`, and `error`, allowing you to respond to the connection state changes. + * + * @fires new - Emitted when the host is created and ready to accept clients. + * @fires waiting-for-client - Emitted when waiting for a client to connect. + * @fires waiting-for-data - Emitted when the host is ready to receive data. + * @fires waiting-for-ice - Emitted when ICE candidates are being gathered. + * @fires receiving-data - Emitted when the host is receiving data from the client. + * @fires file-received - Emitted when a complete file has been received. + * @fires done - Emitted when the transfer is complete. + * @fires error - Emitted when an error occurs during connection or data transfer. + * @fires connected - Emitted when the host successfully connects to a client. + * @fires disconnected - Emitted when the connection is closed. + * + * @extends EventEmitter + */ export class FlottformChannelHost extends EventEmitter { private flottformApi: string | URL; private createClientUrl: (params: { endpointId: string }) => Promise; @@ -22,6 +41,15 @@ export class FlottformChannelHost extends EventEmitter { private dataChannel: RTCDataChannel | null = null; private pollForIceTimer: NodeJS.Timeout | number | null = null; + /** + * Creates an instance of FlottformChannelHost + * + * @param {Object} config - The configuration for setting up the channel for the host. + * @param {flottformApi} - The API endpoint for retrieving connection information. + * @param {createClientUrl} - A function that generates the client URL given an endpoint ID. + * @param {pollTimeForIceInMs} - The time interval (in ms) for polling ICE candidates. + * @param {logger} - Optional logger for logging connection events. + */ constructor({ flottformApi, createClientUrl, @@ -51,6 +79,9 @@ export class FlottformChannelHost extends EventEmitter { this.logger.info(`State changed to: ${newState}`, details == undefined ? '' : details); }; + /** + * Starts the WebRTC connection process for the host. The connection is not established until this method is called. + */ start = async () => { if (this.openPeerConnection) { this.close(); @@ -97,6 +128,11 @@ export class FlottformChannelHost extends EventEmitter { this.setupDataChannelListener(); }; + /** + * Closes the WebRTC connection if it is currently established. + * + * @fires disconnected - Emitted when the connection is successfully closed. + */ close = () => { if (this.openPeerConnection) { this.openPeerConnection.close(); diff --git a/flottform/forms/src/flottform-file-input-client.ts b/flottform/forms/src/flottform-file-input-client.ts index 7b31461..1580d25 100644 --- a/flottform/forms/src/flottform-file-input-client.ts +++ b/flottform/forms/src/flottform-file-input-client.ts @@ -17,6 +17,18 @@ type MetaData = { totalSize: number; }; +/** + * The `FlottformFileInputClient` uses the `FlottformChannelClient` to manage the WebRTC connection and handle the transfer of large files to a peer. + * It listens to various events emitted by `FlottformChannelClient` to implement the file sending process. + * + * @fires connected - Emitted when the connection is successfully established. + * @fires webrtc:connection-impossible - Emitted if the connection to the host cannot be established. + * @fires done - Emitted when the all of the data is sent. + * @fires disconnected - Emitted when the connection is closed. + * @fires error - Emitted when there is an error during the connection. + * + * @extends EventEmitter + */ export class FlottformFileInputClient extends EventEmitter { private channel: FlottformChannelClient | null = null; private inputField: HTMLInputElement; @@ -26,8 +38,19 @@ export class FlottformFileInputClient extends EventEmitter { private currentFileIndex = 0; private currentChunkIndex = 0; private allFilesSent = false; + // @ts-ignore: Unused variable private logger: Logger; + /** + * Creates an instance of FlottformFileInputClient. + * + * @param {Object} config - The configuration for setting up the file input client. + * @param {string} - The unique ID for the WebRTC endpoint. + * @param {HTMLInputElement} - The input field element where files are selected for transfer. + * @param {string} - The API URL for retrieving connection information. + * @param {number} [config.pollTimeForIceInMs=POLL_TIME_IN_MS] - The polling time for ICE candidates in milliseconds. + * @param {Logger} [config.logger=console] - Logger for capturing logs and errors. + */ constructor({ endpointId, fileInput, @@ -52,10 +75,17 @@ export class FlottformFileInputClient extends EventEmitter { this.logger = logger; this.registerListeners(); } + + /** + * Starts the WebRTC connection by invoking the `start` method of the underlying `FlottformChannelClient`. + */ start = () => { this.channel?.start(); }; + /** + * Closes the WebRTC connection by invoking the `close` method of the underlying `FlottformChannelClient`. + */ close = () => { this.channel?.close(); }; @@ -85,6 +115,12 @@ export class FlottformFileInputClient extends EventEmitter { return await Promise.all(files.map(async (file) => await file.arrayBuffer())); }; + /** + * Starts the process of sending files over the WebRTC connection. + * This method prepares the files, creates metadata, and begins the file transfer. + * + * @throws Will throw an error if the metadata or file data is unavailable. + */ sendFiles = async () => { const metaData = this.createMetaData(this.inputField); const filesArrayBuffer = await this.createArrayBuffers(this.inputField); diff --git a/flottform/forms/src/flottform-file-input-host.ts b/flottform/forms/src/flottform-file-input-host.ts index ca19ed2..eac8f65 100644 --- a/flottform/forms/src/flottform-file-input-host.ts +++ b/flottform/forms/src/flottform-file-input-host.ts @@ -14,6 +14,26 @@ type Listeners = BaseListeners & { 'webrtc:waiting-for-file': []; }; +/** + * The `FlottformFileInputHost` class uses the `FlottformChannelHost` to manage the WebRTC connection and handle the reception of large files from a peer. + * + * It listens to various events emitted by `FlottformChannelHost` to implement the file transfer process. + * + * @fires new - Emitted when the host is created and ready to accept clients. + * @fires webrtc:waiting-for-client - Emitted when waiting for a client to connect. + * @fires webrtc:waiting-for-ice - Emitted when ICE candidates are being gathered. + * @fires webrtc:waiting-for-ice - Emitted when host is ready to receive the file(s). + * @fires done - Emitted when the transfer is complete. + * @fires error - Emitted when an error occurs during connection or data transfer. + * @fires connected - Emitted when the host successfully connects to a client. + * @fires disconnected - Emitted when the connection is closed. + * @fires receive - Emitted to signal the start of receiving the file(s). + * @fires progress - Emitted to signal the progress of receiving the file(s). + * @fires endpoint-created - Emitted when the endpoint for a new potential connection is created. + * + * + * @extends EventEmitter + */ export class FlottformFileInputHost extends BaseInputHost { private channel: FlottformChannelHost | null = null; private inputField: HTMLInputElement; @@ -29,6 +49,17 @@ export class FlottformFileInputHost extends BaseInputHost { private link: string = ''; private qrCode: string = ''; + /** + * Creates an instance of FlottformFileInputHost. + * + * @param {Object} config - The configuration for setting up the file input host. + * @param {string | URL} - The API URL for retrieving connection information. + * @param {(params: { endpointId: string }) => Promise} - A function to generate the client URL given an endpoint ID. + * @param {HTMLInputElement} - The input field element where files will be added. + * @param {number} [config.pollTimeForIceInMs=POLL_TIME_IN_MS] - The polling time for ICE candidates in milliseconds. + * @param {Logger} [config.logger=console] - Logger for capturing logs and errors. + * @param {Styles} [config.styles] - Optional styles to be applied. + */ constructor({ flottformApi, createClientUrl, @@ -40,7 +71,6 @@ export class FlottformFileInputHost extends BaseInputHost { createClientUrl: (params: { endpointId: string }) => Promise; inputField: HTMLInputElement; pollTimeForIceInMs?: number; - theme?: (myself: FlottformFileInputHost) => void; logger?: Logger; }) { super(); @@ -56,14 +86,25 @@ export class FlottformFileInputHost extends BaseInputHost { this.registerListeners(); } + /** + * Starts the WebRTC connection by invoking the `start` method of the underlying `FlottformChannelHost`. + */ start = () => { this.channel?.start(); }; + /** + * Closes the WebRTC connection by invoking the `close` method of the underlying `FlottformChannelHost`. + */ close = () => { this.channel?.close(); }; + /** + * Retrieves the connection link (URL) used for establishing a peer connection. + * + * @returns {string} The link for the peer connection. + */ getLink = () => { if (this.link === '') { this.logger.error( @@ -73,6 +114,11 @@ export class FlottformFileInputHost extends BaseInputHost { return this.link; }; + /** + * Retrieves the QR code used for establishing a peer connection. + * + * @returns {string} The QR code for the peer connection. + */ getQrCode = () => { if (this.qrCode === '') { this.logger.error( diff --git a/flottform/forms/src/flottform-text-input-client.ts b/flottform/forms/src/flottform-text-input-client.ts index aea740d..cf0c8dd 100644 --- a/flottform/forms/src/flottform-text-input-client.ts +++ b/flottform/forms/src/flottform-text-input-client.ts @@ -10,10 +10,26 @@ type Listeners = { error: [e: string]; }; +/** + * The `FlottformTextInputClient` uses the `FlottformChannelClient` to establish a WebRTC connection and send text data to a peer. + * It listens to various events emitted by `FlottformChannelClient` to manage connection states and the sending process. + * + * @extends EventEmitter + */ export class FlottformTextInputClient extends EventEmitter { private channel: FlottformChannelClient | null = null; + // @ts-ignore: Unused variable private logger: Logger; + /** + * Creates an instance of FlottformTextInputClient. + * + * @param {Object} config - The configuration for setting up the text input client. + * @param {string} config.endpointId - The unique ID for the WebRTC endpoint. + * @param {string} config.flottformApi - The API URL for retrieving connection information. + * @param {number} [config.pollTimeForIceInMs=POLL_TIME_IN_MS] - The polling time for ICE candidates in milliseconds. + * @param {Logger} [config.logger=console] - Logger for capturing logs and errors. + */ constructor({ endpointId, flottformApi, @@ -35,15 +51,30 @@ export class FlottformTextInputClient extends EventEmitter { this.logger = logger; this.registerListeners(); } + + /** + * Starts the WebRTC connection by invoking the `start` method of the underlying `FlottformChannelClient`. + */ start = () => { this.channel?.start(); }; + /** + * Closes the WebRTC connection by invoking the `close` method of the underlying `FlottformChannelClient`. + */ close = () => { // Should be called once all the text has been sent. this.channel?.close(); }; + /** + * Sends the provided text over the WebRTC connection. + * The text is sent as a single chunk since most texts will not exceed 16 KB in size. + * + * @param {string} text - The text data to be sent. + * @emits sending - Emitted when the text starts to be sent. + * @emits done - Emitted when the text sending process is completed. + */ sendText = (text: string) => { // For now, I didn't handle very large texts since for most use cases the text won't exceed the size of 1 chunk ( 16KB ) this.emit('sending'); diff --git a/flottform/forms/src/flottform-text-input-host.ts b/flottform/forms/src/flottform-text-input-host.ts index 3217647..85934b1 100644 --- a/flottform/forms/src/flottform-text-input-host.ts +++ b/flottform/forms/src/flottform-text-input-host.ts @@ -14,6 +14,15 @@ export class FlottformTextInputHost extends BaseInputHost { private link: string = ''; private qrCode: string = ''; + /** + * Creates an instance of FlottformTextInputHost. + * + * @param {Object} config - The configuration for setting up the text input host. + * @param {string | URL} config.flottformApi - The API URL for retrieving connection information. + * @param {Function} config.createClientUrl - A function to create the client URL with an endpoint ID. + * @param {number} [config.pollTimeForIceInMs=POLL_TIME_IN_MS] - The polling time for ICE candidates in milliseconds. + * @param {Logger} [config.logger=console] - Logger for capturing logs and errors. + */ constructor({ flottformApi, createClientUrl, @@ -37,14 +46,25 @@ export class FlottformTextInputHost extends BaseInputHost { this.registerListeners(); } + /** + * Starts the WebRTC connection by invoking the `start` method of the underlying `FlottformChannelHost`. + */ start = () => { this.channel?.start(); }; + /** + * Closes the WebRTC connection by invoking the `close` method of the underlying `FlottformChannelHost`. + */ close = () => { this.channel?.close(); }; + /** + * Retrieves the link (URL) for the client to connect to the WebRTC host. + * + * @returns {string} The link for the client to connect. If the link is unavailable, an error is logged. + */ getLink = () => { if (this.link === '') { this.logger.error( @@ -54,6 +74,11 @@ export class FlottformTextInputHost extends BaseInputHost { return this.link; }; + /** + * Retrieves the QR code data for the client to scan and connect to the WebRTC host. + * + * @returns {string} The QR code data for client connection. If the QR code is unavailable, an error is logged. + */ getQrCode = () => { if (this.qrCode === '') { this.logger.error( diff --git a/flottform/forms/theme/docs-style.css b/flottform/forms/theme/docs-style.css new file mode 100644 index 0000000..e4a3dd5 --- /dev/null +++ b/flottform/forms/theme/docs-style.css @@ -0,0 +1,37 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap'); + +:root { + --font-sans: 'Roboto', 'Space Grotesk', sans-serif !important; +} + +/* Dark Theme (manual) */ +:root[data-theme='dark'] { + --color-background: #1a2238 !important; + --md-sys-color-surface: #273c75 !important; + --color-text: white !important; +} + +/* Light Theme (manual) */ +:root[data-theme='light'] { + --color-background: #e1e1e1 !important; + --md-sys-color-surface: #fafafa !important; + --color-text: black !important; +} + +/* OS Preference: Dark */ +@media (prefers-color-scheme: dark) { + :root { + --color-background: #1a2238 !important; + --md-sys-color-surface: #273c75 !important; + --color-text: white !important; + } +} + +/* OS Preference: Light */ +@media (prefers-color-scheme: light) { + :root { + --color-background: #e1e1e1 !important; + --md-sys-color-surface: #fafafa !important; + --color-text: black !important; + } +} diff --git a/flottform/forms/typedoc.json b/flottform/forms/typedoc.json new file mode 100644 index 0000000..139a8c9 --- /dev/null +++ b/flottform/forms/typedoc.json @@ -0,0 +1,19 @@ +{ + "entryPoints": [ + "src/flottform-channel-client.ts", + "src/flottform-channel-host.ts", + "src/flottform-file-input-client.ts", + "src/flottform-file-input-host.ts", + "src/flottform-text-input-client.ts", + "src/flottform-text-input-host.ts", + "src/default-component.ts" + ], + "out": "docs", + "tsconfig": "./tsconfig.json", + "includeVersion": true, + "excludePrivate": true, + "excludeProtected": true, + "excludeInternal": true, + "plugin": ["typedoc-material-theme"], + "customCss": "./theme/docs-style.css" +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65e014a..ac6f189 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 3.3.3 prettier-plugin-svelte: specifier: ^3.2.7 - version: 3.2.7(prettier@3.3.3)(svelte@4.2.19) + version: 3.2.7(prettier@3.3.3)(svelte@5.0.0-next.262) prettier-plugin-tailwindcss: specifier: ^0.6.8 version: 0.6.8(prettier-plugin-svelte@3.2.7)(prettier@3.3.3) @@ -122,6 +122,9 @@ importers: '@eslint/js': specifier: ^9.12.0 version: 9.12.0 + '@fontsource/roboto': + specifier: ^5.1.0 + version: 5.1.0 '@types/qrcode': specifier: ^1.5.5 version: 1.5.5 @@ -134,6 +137,12 @@ importers: qrcode: specifier: ^1.5.4 version: 1.5.4 + typedoc: + specifier: ^0.26.10 + version: 0.26.10(typescript@5.6.2) + typedoc-material-theme: + specifier: ^1.1.0 + version: 1.1.0(typedoc@0.26.10) vite: specifier: ^5.4.8 version: 5.4.8 @@ -702,14 +711,14 @@ packages: resolution: {integrity: sha512-g97nQtuEMVlW95xKCZuDun4gSxmBOxf+7yfValqOwATvJL/98RkCacEsBgJlFNtWxO0+FkmoMl3b9kvZpyQ6VA==} dev: true - /@fontsource/unbounded@5.0.21: - resolution: {integrity: sha512-XrmmnUI13/ZBNcylDrILo5LZzGpKjMBTOSk4JIi9lhX2Q58aJDL7typQrOSKYLXSHZjwBFrtK4asnuwRfCt3eA==} - dev: true - /@fontsource/roboto@5.1.0: resolution: {integrity: sha512-cFRRC1s6RqPygeZ8Uw/acwVHqih8Czjt6Q0MwoUoDe9U3m4dH1HmNDRBZyqlMSFwgNAUKgFImncKdmDHyKpwdg==} dev: true + /@fontsource/unbounded@5.0.21: + resolution: {integrity: sha512-XrmmnUI13/ZBNcylDrILo5LZzGpKjMBTOSk4JIi9lhX2Q58aJDL7typQrOSKYLXSHZjwBFrtK4asnuwRfCt3eA==} + dev: true + /@humanfs/core@0.19.0: resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} engines: {node: '>=18.18.0'} @@ -820,6 +829,10 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true + /@material/material-color-utilities@0.2.7: + resolution: {integrity: sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==} + dev: true + /@microsoft/api-extractor-model@7.29.6: resolution: {integrity: sha512-gC0KGtrZvxzf/Rt9oMYD2dHvtN/1KPEYsrQPyMKhLHnlVuO/f4AFN3E4toqZzD2pt4LhkKoYmL2H9tX3yCOyRw==} dependencies: @@ -1194,6 +1207,43 @@ packages: - '@types/node' dev: true + /@shikijs/core@1.22.0: + resolution: {integrity: sha512-S8sMe4q71TJAW+qG93s5VaiihujRK6rqDFqBnxqvga/3LvqHEnxqBIOPkt//IdXVtHkQWKu4nOQNk0uBGicU7Q==} + dependencies: + '@shikijs/engine-javascript': 1.22.0 + '@shikijs/engine-oniguruma': 1.22.0 + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.3 + dev: true + + /@shikijs/engine-javascript@1.22.0: + resolution: {integrity: sha512-AeEtF4Gcck2dwBqCFUKYfsCq0s+eEbCEbkUuFou53NZ0sTGnJnJ/05KHQFZxpii5HMXbocV9URYVowOP2wH5kw==} + dependencies: + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + oniguruma-to-js: 0.4.3 + dev: true + + /@shikijs/engine-oniguruma@1.22.0: + resolution: {integrity: sha512-5iBVjhu/DYs1HB0BKsRRFipRrD7rqjxlWTj4F2Pf+nQSPqc3kcyqFFeZXnBMzDf0HdqaFVvhDRAGiYNvyLP+Mw==} + dependencies: + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + dev: true + + /@shikijs/types@1.22.0: + resolution: {integrity: sha512-Fw/Nr7FGFhlQqHfxzZY8Cwtwk5E9nKDUgeLjZgt3UuhcM3yJR9xj3ZGNravZZok8XmEZMiYkSMTPlPkULB8nww==} + dependencies: + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + dev: true + + /@shikijs/vscode-textmate@9.3.0: + resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} + dev: true + /@sveltejs/adapter-node@5.2.5(@sveltejs/kit@2.6.2): resolution: {integrity: sha512-FVeysFqeIlKFpDF1Oj38gby34f6uA9FuXnV330Z0RHmSyOR9JzJs70/nFKy1Ue3fWtf7S0RemOrP66Vr9Jcmew==} peerDependencies: @@ -1202,7 +1252,7 @@ packages: '@rollup/plugin-commonjs': 28.0.0(rollup@4.24.0) '@rollup/plugin-json': 6.1.0(rollup@4.24.0) '@rollup/plugin-node-resolve': 15.3.0(rollup@4.24.0) - '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@5.0.0-next.262)(vite@5.4.8) + '@sveltejs/kit': 2.6.2(@sveltejs/vite-plugin-svelte@3.1.2)(svelte@5.0.0-next.136)(vite@5.4.8) rollup: 4.24.0 dev: true @@ -1386,6 +1436,12 @@ packages: resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} dev: true + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.3 + dev: true + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -1396,6 +1452,12 @@ packages: '@types/geojson': 7946.0.14 dev: true + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + dependencies: + '@types/unist': 3.0.3 + dev: true + /@types/mute-stream@0.0.4: resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} dependencies: @@ -1436,6 +1498,10 @@ packages: resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} dev: true + /@types/unist@3.0.3: + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + dev: true + /@types/which@2.0.2: resolution: {integrity: sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==} dev: true @@ -1584,6 +1650,10 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + /@vitejs/plugin-basic-ssl@1.1.0(vite@5.4.8): resolution: {integrity: sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==} engines: {node: '>=14.6.0'} @@ -2205,6 +2275,10 @@ packages: resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==} dev: true + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: true + /chai@5.1.1: resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} engines: {node: '>=12'} @@ -2238,6 +2312,14 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + dev: true + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + dev: true + /check-error@2.1.1: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} @@ -2315,16 +2397,6 @@ packages: wrap-ansi: 7.0.0 dev: true - /code-red@1.0.4: - resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - '@types/estree': 1.0.6 - acorn: 8.12.1 - estree-walker: 3.0.3 - periscopic: 3.1.0 - dev: true - /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2346,6 +2418,10 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -2438,14 +2514,6 @@ packages: resolution: {integrity: sha512-C2AugXIpRGQTxaCW0N7n5jD/p5irUmCrwl03TrnMFBHDbdq44CFWR2zO7rK9xPN4Eo3pUxC4vQzQgbIpzrD1PQ==} dev: true - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - dev: true - /css-value@0.0.1: resolution: {integrity: sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==} dev: true @@ -2534,6 +2602,12 @@ packages: resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} dev: true + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dependencies: + dequal: 2.0.3 + dev: true + /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -3229,6 +3303,28 @@ packages: function-bind: 1.1.2 dev: true + /hast-util-to-html@9.0.3: + resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + dev: true + + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + dependencies: + '@types/hast': 3.0.4 + dev: true + /he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -3238,6 +3334,10 @@ packages: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} dev: true + /html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + dev: true + /htmlfy@0.2.1: resolution: {integrity: sha512-HoomFHQ3av1uhq+7FxJTq4Ns0clAD+tGbQNrSd0WFY3UAjjUk6G3LaWEqdgmIXYkY4pexZiyZ3ykZJhQlM0J5A==} dev: true @@ -3533,6 +3633,12 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true + /linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + dependencies: + uc.micro: 2.1.0 + dev: true + /local-pkg@0.5.0: resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} engines: {node: '>=14'} @@ -3614,6 +3720,10 @@ packages: engines: {node: '>=12'} dev: true + /lunr@2.3.9: + resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} + dev: true + /lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -3625,8 +3735,34 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + /markdown-it@14.1.0: + resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==} + hasBin: true + dependencies: + argparse: 2.0.1 + entities: 4.5.0 + linkify-it: 5.0.0 + mdurl: 2.0.0 + punycode.js: 2.3.1 + uc.micro: 2.1.0 + dev: true + + /mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + dev: true + + /mdurl@2.0.0: + resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} dev: true /merge2@1.4.1: @@ -3634,6 +3770,33 @@ packages: engines: {node: '>= 8'} dev: true + /micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: true + + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: true + + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: true + + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: true + + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: true + /micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3809,6 +3972,12 @@ packages: wrappy: 1.0.2 dev: true + /oniguruma-to-js@0.4.3: + resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + dependencies: + regex: 4.3.3 + dev: true + /optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3959,14 +4128,6 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true - /periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - dependencies: - '@types/estree': 1.0.6 - estree-walker: 3.0.3 - is-reference: 3.0.2 - dev: true - /picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} dev: true @@ -4125,16 +4286,6 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@4.2.19): - resolution: {integrity: sha512-/Dswx/ea0lV34If1eDcG3nulQ63YNr5KPDfMsjbdtpSWOxKKJ7nAc2qlVuYwEvCr4raIuredNoR7K4JCkmTGaQ==} - peerDependencies: - prettier: ^3.0.0 - svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 - dependencies: - prettier: 3.3.3 - svelte: 4.2.19 - dev: true - /prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@5.0.0-next.136): resolution: {integrity: sha512-/Dswx/ea0lV34If1eDcG3nulQ63YNr5KPDfMsjbdtpSWOxKKJ7nAc2qlVuYwEvCr4raIuredNoR7K4JCkmTGaQ==} peerDependencies: @@ -4211,7 +4362,7 @@ packages: optional: true dependencies: prettier: 3.3.3 - prettier-plugin-svelte: 3.2.7(prettier@3.3.3)(svelte@4.2.19) + prettier-plugin-svelte: 3.2.7(prettier@3.3.3)(svelte@5.0.0-next.262) dev: true /prettier@3.3.3: @@ -4243,6 +4394,10 @@ packages: engines: {node: '>=0.4.0'} dev: true + /property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + dev: true + /proxy-agent@6.4.0: resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} engines: {node: '>= 14'} @@ -4274,6 +4429,11 @@ packages: once: 1.4.0 dev: true + /punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + dev: true + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4361,6 +4521,10 @@ packages: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} dev: true + /regex@4.3.3: + resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==} + dev: true + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4508,6 +4672,17 @@ packages: engines: {node: '>=8'} dev: true + /shiki@1.22.0: + resolution: {integrity: sha512-/t5LlhNs+UOKQCYBtl5ZsH/Vclz73GIqT2yQsCBygr8L/ppTdmpL4w3kPLoZJbMKVWtoG77Ue1feOjZfDxvMkw==} + dependencies: + '@shikijs/core': 1.22.0 + '@shikijs/engine-javascript': 1.22.0 + '@shikijs/engine-oniguruma': 1.22.0 + '@shikijs/types': 1.22.0 + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + dev: true + /siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true @@ -4561,6 +4736,10 @@ packages: requiresBuild: true dev: true + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + dev: true + /spacetrim@0.11.39: resolution: {integrity: sha512-S/baW29azJ7py5ausQRE2S6uEDQnlxgMHOEEq4V770ooBDD1/9kZnxRcco/tjZYuDuqYXblCk/r3N13ZmvHZ2g==} dev: true @@ -4640,6 +4819,13 @@ packages: safe-buffer: 5.2.1 dev: true + /stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + dev: true + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4793,26 +4979,6 @@ packages: svelte: 5.0.0-next.262 dev: true - /svelte@4.2.19: - resolution: {integrity: sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==} - engines: {node: '>=16'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - '@types/estree': 1.0.6 - acorn: 8.12.1 - aria-query: 5.3.2 - axobject-query: 4.1.0 - code-red: 1.0.4 - css-tree: 2.3.1 - estree-walker: 3.0.3 - is-reference: 3.0.2 - locate-character: 3.0.0 - magic-string: 0.30.11 - periscopic: 3.1.0 - dev: true - /svelte@5.0.0-next.136: resolution: {integrity: sha512-M3jHAIfWZ7K+hjZdvu2p53ZtWE843yubxJfjxeQw9XiwMYG5z6quCA5u8r23GrxAp20JBl36B6ucbZvLUf0Z/g==} engines: {node: '>=18'} @@ -4984,6 +5150,10 @@ packages: url-parse: 1.5.10 dev: true + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: true + /ts-api-utils@1.3.0(typescript@5.6.2): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -5028,6 +5198,31 @@ packages: engines: {node: '>=16'} dev: true + /typedoc-material-theme@1.1.0(typedoc@0.26.10): + resolution: {integrity: sha512-LLWGVb8w+i+QGnsu/a0JKjcuzndFQt/UeGVOQz0HFFGGocROEHv5QYudIACrj+phL2LDwH05tJx0Ob3pYYH2UA==} + engines: {node: '>=18.0.0', npm: '>=8.6.0'} + peerDependencies: + typedoc: ^0.25.13 || ^0.26.3 + dependencies: + '@material/material-color-utilities': 0.2.7 + typedoc: 0.26.10(typescript@5.6.2) + dev: true + + /typedoc@0.26.10(typescript@5.6.2): + resolution: {integrity: sha512-xLmVKJ8S21t+JeuQLNueebEuTVphx6IrP06CdV7+0WVflUSW3SPmR+h1fnWVdAR/FQePEgsSWCUHXqKKjzuUAw==} + engines: {node: '>= 18'} + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x + dependencies: + lunr: 2.3.9 + markdown-it: 14.1.0 + minimatch: 9.0.5 + shiki: 1.22.0 + typescript: 5.6.2 + yaml: 2.5.1 + dev: true + /typescript@5.4.2: resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} @@ -5040,6 +5235,10 @@ packages: hasBin: true dev: true + /uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + dev: true + /ufo@1.5.4: resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} dev: true @@ -5060,6 +5259,39 @@ packages: engines: {node: '>=18.17'} dev: true + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + dependencies: + '@types/unist': 3.0.3 + dev: true + + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.3 + dev: true + + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.3 + dev: true + + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + dev: true + + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: true + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -5112,6 +5344,20 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + dev: true + + /vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + dev: true + /vite-node@2.1.2: resolution: {integrity: sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -5538,3 +5784,7 @@ packages: /zod@3.23.8: resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} dev: true + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + dev: true