### Install Dependencies Source: https://github.com/bloombooks/comical-js/blob/master/README.md Install project dependencies using yarn. This is a one-time setup step for development. ```bash yarn ``` -------------------------------- ### Install Comical-JS Source: https://github.com/bloombooks/comical-js/blob/master/README.md Install the Comical-JS library using yarn. ```bash yarn add comicaljs ``` -------------------------------- ### Launch Storybook Source: https://github.com/bloombooks/comical-js/blob/master/README.md Launch Storybook to view examples and develop ComicalJS components. This opens a browser window for interactive testing. ```bash yarn storybook ``` -------------------------------- ### Configure Bubble Styles with setBubbleSpec Source: https://context7.com/bloombooks/comical-js/llms.txt Demonstrates setting various bubble styles including 'speech', 'shout', 'thought', 'pointedArcs', 'circle', 'caption', 'rectangle', and 'none'. Each example shows the required BubbleSpec properties and how to apply them. ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("container") as HTMLElement; const div = document.getElementById("text") as HTMLElement; const bubble = new Bubble(div); const tail = Bubble.makeDefaultTail(div); // "speech" — smooth elliptical speech bubble, arc tail bubble.setBubbleSpec({ version:"1.0", style:"speech", tails:[tail], level:1 }); // "shout" — jagged/exploding shape (SVG), arc tail bubble.setBubbleSpec({ version:"1.0", style:"shout", tails:[tail], level:1 }); // "thought" — smooth thought bubble, chain-of-circles thought tail bubble.setBubbleSpec({ version:"1.0", style:"thought", tails:[tail], level:1 }); // "pointedArcs" — inward-arced border (computed), no tail by default bubble.setBubbleSpec({ version:"1.0", style:"pointedArcs", tails:[], level:1 }); // "circle" — circular bubble (computed), arc tail bubble.setBubbleSpec({ version:"1.0", style:"circle", tails:[tail], level:1 }); // "caption" — rectangular box with optional gradient/shadow, line tail bubble.setBubbleSpec({ version:"1.0", style:"caption", tails:[], backgroundColors:["#FFF","#DFB28B"], shadowOffset:5, level:1 }); // "rectangle" — plain rectangle with 1px border, line tail bubble.setBubbleSpec({ version:"1.0", style:"rectangle", tails:[], level:1 }); // "none" — invisible/transparent container (no bubble shape), line tail bubble.setBubbleSpec({ version:"1.0", style:"none", tails:[], backgroundColors:["transparent"], level:1 }); Comical.update(container); ``` -------------------------------- ### Start Editing Mode Source: https://github.com/bloombooks/comical-js/blob/master/README.md Enable interactive editing of bubbles for specified parent elements. This allows users to click and drag handles to move bubble tails. ```javascript Comical.startEditing([parent]); ``` -------------------------------- ### Get Default Bubble Specification Source: https://context7.com/bloombooks/comical-js/llms.txt Generate a default BubbleSpec for an element, automatically configuring tails for speech styles or presets for captions. This is useful for creating new bubbles with sensible defaults. ```typescript import { Bubble } from "comicaljs"; const textDiv = document.getElementById("bubble-text") as HTMLElement; // Get a default speech bubble spec (automatically places the tail) const speechSpec = Bubble.getDefaultBubbleSpec(textDiv, "speech"); console.log(speechSpec); // { // version: "1.0", // style: "speech", // tails: [{ tipX: 250, tipY: 300, midpointX: 200, midpointY: 220, autoCurve: true }], // level: 1 // } // A caption with no tail gets preset colors and shadow const captionSpec = Bubble.getDefaultBubbleSpec(textDiv, "caption"); // { style: "caption", backgroundColors: ["#FFFFFF", "#DFB28B"], shadowOffset: 5, tails: [] } // Apply the spec: Bubble.setBubbleSpec(textDiv, speechSpec); ``` -------------------------------- ### Start Comical Editing Mode Source: https://context7.com/bloombooks/comical-js/llms.txt Activates editing mode for one or more parent HTML elements. This replaces existing SVGs with interactive canvases and reads 'data-bubble' attributes to render bubbles. ```typescript import { Comical, Bubble } from "comicaljs"; // Set up HTML: a parent div containing a text div const imageContainer = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("speech-text") as HTMLElement; // Assign an initial bubble spec to the text element Bubble.setBubbleSpec(textDiv, Bubble.getDefaultBubbleSpec(textDiv, "speech")); // Start interactive editing on the container (canvas is overlaid) Comical.startEditing([imageContainer]); // User can now drag tail handles; data-bubble attributes update automatically. // To edit multiple containers (e.g., two images on a page): const container2 = document.getElementById("image-container-2") as HTMLElement; Comical.startEditing([imageContainer, container2]); ``` -------------------------------- ### Update Parent Element Source: https://github.com/bloombooks/comical-js/blob/master/README.md Update the visible bubbles on a parent element after changes to bubble specifications or DOM structure. This should be called after starting editing and before stopping it. ```javascript Comical.update(parent); ``` -------------------------------- ### Get Bubble Specification Source: https://github.com/bloombooks/comical-js/blob/master/README.md Retrieve the BubbleSpec from an element's data-comical attribute. This is useful for converting between the attribute's JSON representation and the BubbleSpec object. ```javascript Bubble.getBubbleSpec(element); ``` -------------------------------- ### Get Comical Bubble Hit by Point Source: https://context7.com/bloombooks/comical-js/llms.txt Retrieves the highest-level `Bubble` object hit by a point (x, y) within a container, or `undefined` if no bubble is hit. Invisible bubbles are ignored. Can optionally exclude bubbles based on enablement or a CSS selector. Ensure Comical.startEditing is called first. ```typescript import { Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); container.addEventListener("click", (e: MouseEvent) => { const bubble = Comical.getBubbleHit(container, e.offsetX, e.offsetY); if (bubble) { console.log("Clicked bubble style:", bubble.getStyle()); // Activate the hit bubble for editing Comical.activateBubble(bubble); } }); // Ignoring bubbles marked with a specific class: const hitBubble = Comical.getBubbleHit(container, 100, 200, false, ".overlay-image"); ``` -------------------------------- ### Build Project Source: https://github.com/bloombooks/comical-js/blob/master/README.md Create a 'dest' directory containing the files for npm publishing. This command builds the current version of Comical.js. ```bash yarn build ``` -------------------------------- ### Comical.setUserInterfaceProperties Source: https://context7.com/bloombooks/comical-js/llms.txt Configures global UI properties for Comical, such as tail handle colors. Must be called before `startEditing()`. ```APIDOC ## Comical.setUserInterfaceProperties(props: { tailHandleColor: string }): void ### Description Configures global UI properties for Comical. Currently supports customizing the color of tail drag handles. Must be called before `startEditing()`; does not retroactively update existing handles. ### Method `Comical.setUserInterfaceProperties` ### Parameters #### Request Body - **props** (object) - An object containing UI properties to set. - **tailHandleColor** (string) - The desired color for tail drag handles (e.g., "blue"). ### Request Example ```typescript import { Comical } from "comicaljs"; Comical.setUserInterfaceProperties({ tailHandleColor: "blue" }); const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); ``` ### Response This method does not return a value (`void`). ``` -------------------------------- ### Supported Bubble Styles Source: https://context7.com/bloombooks/comical-js/llms.txt Provides an overview of all available `style` values for bubbles, detailing their visual appearance and tail behavior. ```APIDOC ### Supported Bubble Styles This section outlines the different bubble styles available in Comical JS and how to apply them using `setBubbleSpec`. - **speech**: A smooth elliptical speech bubble with an arc tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"speech", tails:[tail], level:1 }); ``` - **shout**: A jagged or exploding shape rendered using SVG, with an arc tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"shout", tails:[tail], level:1 }); ``` - **thought**: A smooth thought bubble with a chain-of-circles thought tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"thought", tails:[tail], level:1 }); ``` - **pointedArcs**: Features an inward-arced border (computed) and no tail by default. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"pointedArcs", tails:[], level:1 }); ``` - **circle**: A circular bubble shape with an arc tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"circle", tails:[tail], level:1 }); ``` - **caption**: A rectangular box that can include optional gradients and shadows, using a line tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"caption", tails:[], backgroundColors:["#FFF","#DFB28B"], shadowOffset:5, level:1 }); ``` - **rectangle**: A plain rectangle with a 1px border and a line tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"rectangle", tails:[], level:1 }); ``` - **none**: An invisible or transparent container that does not render a bubble shape, using a line tail. ```typescript bubble.setBubbleSpec({ version:"1.0", style:"none", tails:[], backgroundColors:["transparent"], level:1 }); ``` ### Request Example ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("container") as HTMLElement; const div = document.getElementById("text") as HTMLElement; const bubble = new Bubble(div); const tail = Bubble.makeDefaultTail(div); // Example: Setting a 'speech' style bubble bubble.setBubbleSpec({ version:"1.0", style:"speech", tails:[tail], level:1 }); Comical.update(container); ``` ``` -------------------------------- ### Configure Comical UI Properties Source: https://context7.com/bloombooks/comical-js/llms.txt Sets global UI properties for Comical, such as the tail drag handle color. This must be called before `Comical.startEditing()`. Changes do not affect existing handles. ```typescript import { Comical } from "comicaljs"; // Customize handle color to match application theme Comical.setUserInterfaceProperties({ tailHandleColor: "blue" }); const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); // Tail handles will now appear blue instead of the default orange ``` -------------------------------- ### Comical.initializeChild(childElement: HTMLElement, parentElement: HTMLElement): void Source: https://context7.com/bloombooks/comical-js/llms.txt Adds a new bubble as a child of an existing bubble family. The child bubble inherits the parent's style and visual properties, and a joiner tail is automatically drawn between them if they do not overlap. Bubbles in the same family share the same `level` and have unique sequential `order` values. ```APIDOC ## `Comical.initializeChild(childElement: HTMLElement, parentElement: HTMLElement): void` ### Description Adds a new bubble as a child of an existing bubble family. The child bubble inherits the parent's style and visual properties, and a joiner tail is automatically drawn between them when they do not overlap. All bubbles in the same family share the same `level` and have unique sequential `order` values; bubbles in a family merge their outlines. ### Method `initializeChild` ### Parameters #### Path Parameters - **childElement** (HTMLElement) - Required - The HTML element representing the new child bubble. - **parentElement** (HTMLElement) - Required - The HTML element representing the parent bubble to which the child will be attached. ### Request Example ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const parentDiv = document.getElementById("bubble-parent") as HTMLElement; const childDiv = document.getElementById("bubble-child") as HTMLElement; // Set up the parent bubble first const parentBubble = new Bubble(parentDiv); parentBubble.setBubbleSpec({ version: "1.0", style: "speech", tails: [Bubble.makeDefaultTail(parentDiv)], level: 1, backgroundColors: ["rgba(200,255,255,0.5)"] }); // Activate editing, then link child to parent Comical.startEditing([container]); Comical.initializeChild(childDiv, parentDiv); // childDiv now shares the parent's level, style, and background; // a joiner tail connects the two bubbles when they don't overlap. Comical.stopEditing(); ``` ``` -------------------------------- ### Comical.stopEditing Source: https://context7.com/bloombooks/comical-js/llms.txt Ends editing mode on all active containers. Each canvas is converted to a static SVG with class `comical-generated` and inserted into the DOM in place of the canvas. The SVG is fully self-contained and requires no JavaScript to display. Subsequent calls to `startEditing` can restore editing using the persisted `data-bubble` attributes. ```APIDOC ## Comical.stopEditing(): void ### Description Ends editing mode on all active containers. Each canvas is converted to a static SVG with class `comical-generated` and inserted into the DOM in place of the canvas. The SVG is fully self-contained and requires no JavaScript to display. Subsequent calls to `startEditing` can restore editing using the persisted `data-bubble` attributes. ### Method `Comical.stopEditing` ``` -------------------------------- ### Initialize Child Bubble Source: https://context7.com/bloombooks/comical-js/llms.txt Adds a new bubble as a child of an existing bubble, inheriting its style. A joiner tail is automatically drawn if they don't overlap. Ensure `Comical.startEditing` has been called. ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const parentDiv = document.getElementById("bubble-parent") as HTMLElement; const childDiv = document.getElementById("bubble-child") as HTMLElement; // Set up the parent bubble first const parentBubble = new Bubble(parentDiv); parentBubble.setBubbleSpec({ version: "1.0", style: "speech", tails: [Bubble.makeDefaultTail(parentDiv)], level: 1, backgroundColors: ["rgba(200,255,255,0.5)"] }); // Activate editing, then link child to parent Comical.startEditing([container]); Comical.initializeChild(childDiv, parentDiv); // childDiv now shares the parent's level, style, and background; // a joiner tail connects the two bubbles when they don't overlap. Comical.stopEditing(); ``` -------------------------------- ### Comical.activateElement(contentElement: HTMLElement | undefined): void Source: https://context7.com/bloombooks/comical-js/llms.txt Makes the bubble wrapping a specific content element "active," showing its tail drag handles. Only one bubble can be active at a time; calling this method deactivates the previously active bubble. Pass `undefined` to deactivate all. ```APIDOC ## `Comical.activateElement(contentElement: HTMLElement | undefined): void` ### Description Makes the bubble wrapping a specific content element "active," showing its tail drag handles. Only one bubble is active at a time; calling this deactivates the previously active bubble. Pass `undefined` to deactivate all. ### Method `activateElement` ### Parameters #### Path Parameters - **contentElement** (HTMLElement | undefined) - Required - The content element whose associated bubble should be activated, or `undefined` to deactivate all. ### Request Example ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv1 = document.getElementById("bubble-1") as HTMLElement; const textDiv2 = document.getElementById("bubble-2") as HTMLElement; Comical.startEditing([container]); // Show handles for bubble-1 (e.g., when a user clicks the text element) textDiv1.addEventListener("click", () => { Comical.activateElement(textDiv1); }); textDiv2.addEventListener("click", () => { Comical.activateElement(textDiv2); }); // Deactivate all handles (e.g., clicking the background) container.addEventListener("click", (e) => { if (e.target === container) { Comical.activateElement(undefined); } }); ``` ``` -------------------------------- ### Set Default Bubble Specification Source: https://github.com/bloombooks/comical-js/blob/master/README.md Set a default bubble specification for a child element. Use 'speech' for a speech bubble. ```javascript Bubble.setBubbleSpec(child, Bubble.getDefaultBubbleSpec(child, "speech")); ``` -------------------------------- ### Listen for Active Bubble Changes Source: https://context7.com/bloombooks/comical-js/llms.txt Registers a callback that fires when the active bubble changes. Use this to sync external UI elements with the selected bubble. The callback receives the content `HTMLElement` or `undefined`. ```typescript import { Comical } from "comicaljs"; const styleLabel = document.getElementById("current-style-label")!; // Register a listener before starting to edit Comical.setActiveBubbleListener((activeElement) => { if (activeElement) { const spec = JSON.parse( activeElement.getAttribute("data-bubble")!.replace(/`/g, '"') ); styleLabel.textContent = `Active bubble style: ${spec.style}`; } else { styleLabel.textContent = "No bubble selected"; } }); const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); ``` -------------------------------- ### Define BubbleSpec and TailSpec Interfaces Source: https://context7.com/bloombooks/comical-js/llms.txt These interfaces define the structure for bubble and tail configurations, which are stored in the data-bubble DOM attribute. BubbleSpec requires version, style, and tails, while BubbleSpecPattern allows for partial updates. ```typescript import { BubbleSpec, TailSpec, BubbleSpecPattern } from "comicaljs"; // Full BubbleSpec — required fields: version, style, tails const speechBubble: BubbleSpec = { version: "1.0", style: "speech", // "speech"|"shout"|"thought"|"caption"|"caption-withTail"| // "circle"|"rectangle"|"pointedArcs"|"none" tails: [ { tipX: 300, // tail tip coordinates (relative to parent container) tipY: 400, midpointX: 250, // control point for arc curvature midpointY: 340, autoCurve: true, // auto-adjust midpoint as tip/root move joiner: false // true for child-to-parent connector tails } ], level: 1, // z-index layer; bubbles at same level merge outlines order: 1, // position within a bubble family (1 = patriarch) backgroundColors: ["white"], // solid or gradient (top→bottom) outerBorderColor: "red", // optional decorative outer border shadowOffset: 0, // drop shadow distance in px cornerRadiusX: 8, // rounded corners (caption/rectangle only) cornerRadiusY: 8 }; // BubbleSpecPattern — all fields optional, used for partial updates const partialUpdate: BubbleSpecPattern = { style: "thought", backgroundColors: ["rgba(241,235,156,0.5)"] }; ``` -------------------------------- ### Comical.startEditing Source: https://context7.com/bloombooks/comical-js/llms.txt Activates editing mode on one or more parent container elements. For each parent, Comical replaces any existing static SVG with an interactive `` element, reads all child elements that have a `data-bubble` attribute, and renders their bubbles with draggable tail handles using paper.js. Multiple containers can be active simultaneously. ```APIDOC ## Comical.startEditing(parents: HTMLElement[]): void ### Description Activates editing mode on one or more parent container elements. For each parent, Comical replaces any existing static SVG with an interactive `` element, reads all child elements that have a `data-bubble` attribute, and renders their bubbles with draggable tail handles using paper.js. Multiple containers can be active simultaneously. ### Method `Comical.startEditing` ### Parameters #### Path Parameters - **parents** (HTMLElement[]) - Required - An array of parent HTML elements to activate editing on. ``` -------------------------------- ### Bubble.makeDefaultTail Source: https://context7.com/bloombooks/comical-js/llms.txt A static helper method to compute a default TailSpec for a given HTML element. It intelligently places the tail tip outside the element's bounding box, prioritizing horizontal space and adjusting vertical position as needed. The method automatically sets `autoCurve` to true. ```APIDOC ## Bubble.makeDefaultTail(targetDiv: HTMLElement): TailSpec ### Description Calculates a reasonable default `TailSpec` for a given element. The tail tip is placed outside the element's bounding box, preferring the horizontal direction with the most space, and slightly below (or above if needed) the element. Returns a `TailSpec` with `autoCurve: true`. ### Parameters #### Path Parameters - **targetDiv** (HTMLElement) - Required - The HTML element for which to calculate the default tail. ### Returns - **TailSpec** - An object containing the calculated tail specifications, including `tipX`, `tipY`, `midpointX`, `midpointY`, and `autoCurve`. ### Request Example ```typescript import { Bubble } from "comicaljs"; const textDiv = document.getElementById("bubble-text") as HTMLElement; // Compute a default tail pointing away from the element const tail = Bubble.makeDefaultTail(textDiv); console.log(tail); // { tipX: 300, tipY: 280, midpointX: 250, midpointY: 220, autoCurve: true } // Override specific values if desired: tail.tipX = 50; tail.tipY = 400; // Use it in a spec: const bubble = new Bubble(textDiv); bubble.setBubbleSpec({ version: "1.0", style: "speech", tails: [tail], level: 1 }); ``` ``` -------------------------------- ### Update Bubbles After DOM Changes Source: https://context7.com/bloombooks/comical-js/llms.txt Call this after programmatic changes to the DOM or bubble specs to re-render bubbles. Ensure `Comical.startEditing` has been called. ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("speech-text") as HTMLElement; Comical.startEditing([container]); // Change the bubble style dynamically const bubble = new Bubble(textDiv); bubble.mergeWithNewBubbleProps({ style: "thought" }); // Bubble spec is persisted to data-bubble attribute; now redraw the canvas: Comical.update(container); // The canvas now shows a thought bubble instead of the previous style ``` -------------------------------- ### BubbleSpec and TailSpec Interfaces Source: https://context7.com/bloombooks/comical-js/llms.txt Defines the structure for `BubbleSpec` and `TailSpec` interfaces, which represent the persistent state of a bubble and its tails. These specifications are stored in the `data-bubble` DOM attribute. ```APIDOC ### `BubbleSpec` and `TailSpec` interfaces #### `BubbleSpec` Interface Represents the full specification for a bubble. - **version** (string) - Required - The version of the BubbleSpec. - **style** (string) - Required - The visual style of the bubble. Supported values include: `speech`, `shout`, `thought`, `caption`, `caption-withTail`, `circle`, `rectangle`, `pointedArcs`, `none`. - **tails** (Array) - Required - An array of tail specifications. - **level** (number) - Optional - The z-index layer for the bubble. Bubbles at the same level merge outlines. - **order** (number) - Optional - The position within a bubble family (1 is the patriarch). - **backgroundColors** (Array) - Optional - An array of colors for solid or gradient backgrounds (top to bottom). - **outerBorderColor** (string) - Optional - A color for an optional decorative outer border. - **shadowOffset** (number) - Optional - The distance for a drop shadow in pixels. - **cornerRadiusX** (number) - Optional - The X-axis radius for rounded corners (used for caption/rectangle styles). - **cornerRadiusY** (number) - Optional - The Y-axis radius for rounded corners (used for caption/rectangle styles). #### `TailSpec` Interface Represents the specification for a bubble's tail. - **tipX** (number) - Required - The X-coordinate of the tail tip relative to the parent container. - **tipY** (number) - Required - The Y-coordinate of the tail tip relative to the parent container. - **midpointX** (number) - Optional - The X-coordinate of the control point for arc curvature. - **midpointY** (number) - Optional - The Y-coordinate of the control point for arc curvature. - **autoCurve** (boolean) - Optional - If true, the midpoint is automatically adjusted as the tip and root move. - **joiner** (boolean) - Optional - If true, this tail acts as a connector from a child to a parent bubble. #### `BubbleSpecPattern` Interface Used for partial updates to a `BubbleSpec`. All fields are optional. ### Request Example ```typescript import { BubbleSpec, TailSpec, BubbleSpecPattern } from "comicaljs"; // Full BubbleSpec — required fields: version, style, tails const speechBubble: BubbleSpec = { version: "1.0", style: "speech", // "speech"|"shout"|"thought"|"caption"|"caption-withTail"| // "circle"|"rectangle"|"pointedArcs"|"none" tails: [ { tipX: 300, // tail tip coordinates (relative to parent container) tipY: 400, midpointX: 250, // control point for arc curvature midpointY: 340, autoCurve: true, // auto-adjust midpoint as tip/root move joiner: false // true for child-to-parent connector tails } ], level: 1, // z-index layer; bubbles at same level merge outlines order: 1, // position within a bubble family (1 = patriarch) backgroundColors: ["white"], // solid or gradient (top→bottom) outerBorderColor: "red", // optional decorative outer border shadowOffset: 0, // drop shadow distance in px cornerRadiusX: 8, // rounded corners (caption/rectangle only) cornerRadiusY: 8 }; // BubbleSpecPattern — all fields optional, used for partial updates const partialUpdate: BubbleSpecPattern = { style: "thought", backgroundColors: ["rgba(241,235,156,0.5)"] }; ``` ``` -------------------------------- ### Generate Default TailSpec with Bubble.makeDefaultTail Source: https://context7.com/bloombooks/comical-js/llms.txt Use this static helper to automatically calculate a TailSpec. It places the tail tip outside the target element, favoring the direction with more space and adjusting vertical position. Returns a TailSpec with autoCurve set to true. ```typescript import { Bubble } from "comicaljs"; const textDiv = document.getElementById("bubble-text") as HTMLElement; // Compute a default tail pointing away from the element const tail = Bubble.makeDefaultTail(textDiv); console.log(tail); // { tipX: 300, tipY: 280, midpointX: 250, midpointY: 220, autoCurve: true } // Override specific values if desired: tail.tipX = 50; tail.tipY = 400; // Use it in a spec: const bubble = new Bubble(textDiv); bubble.setBubbleSpec({ version: "1.0", style: "speech", tails: [tail], level: 1 }); ``` -------------------------------- ### Merge Bubble Properties with Partial Updates Source: https://context7.com/bloombooks/comical-js/llms.txt Intelligently update a bubble's existing spec with new properties, preserving custom settings while applying defaults for changed styles. This is the recommended method for modifying bubble appearance or style. ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("bubble-text") as HTMLElement; const bubble = new Bubble(textDiv); Comical.startEditing([container]); // Switch to thought style — tail position is preserved if user moved it, // background color resets to thought-style default bubble.mergeWithNewBubbleProps({ style: "thought" }); Comical.update(container); // Just change the background color, keep everything else bubble.mergeWithNewBubbleProps({ backgroundColors: ["#1d94a4"] }); Comical.update(container); // Add rounded corners to a caption or rectangle bubble.mergeWithNewBubbleProps({ cornerRadiusX: 8, cornerRadiusY: 8 }); Comical.update(container); ``` -------------------------------- ### bubble.setBubbleSpec Source: https://context7.com/bloombooks/comical-js/llms.txt Sets the complete BubbleSpec for a bubble instance and persists it to the element's 'data-bubble' attribute. Requires Comical editing to be active for visual updates. ```APIDOC ## bubble.setBubbleSpec(spec: BubbleSpec): void ### Description Sets the bubble's full spec and immediately persists it to the element's `data-bubble` attribute. The spec must contain at minimum `version`, `style`, `tails`, and `level`. If Comical editing is active, call `Comical.update(container)` after this to redraw. ### Method `bubble.setBubbleSpec` ### Parameters #### Path Parameters - **spec** (BubbleSpec) - Required - The complete bubble specification object. - **version** (string) - Required - The version of the bubble spec. - **style** (string) - Required - The style of the bubble. - **tails** (Array) - Required - An array of tail configurations. - **level** (number) - Required - The z-level for layering. - **backgroundColors** (Array) - Optional - Background colors for the bubble. - **outerBorderColor** (string) - Optional - The color of the outer border. - **shadowOffset** (number) - Optional - Offset for the shadow effect. ### Request Example ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("bubble-text") as HTMLElement; const bubble = new Bubble(textDiv); bubble.setBubbleSpec({ version: "1.0", style: "shout", tails: [{ tipX: 420, tipY: 400, midpointX: 320, midpointY: 375, autoCurve: true }], level: 2, backgroundColors: ["rgba(255,230,0,0.85)"], outerBorderColor: "red", shadowOffset: 4 }); // If editing is already active, redraw: Comical.update(container); ``` ``` -------------------------------- ### Set Bubble Specification Source: https://github.com/bloombooks/comical-js/blob/master/README.md Set the BubbleSpec for an element. This can be done while Comical editing is active to change bubble properties. ```javascript Bubble.setBubbleSpec(element, spec); ``` -------------------------------- ### Bubble.getDefaultBubbleSpec Source: https://context7.com/bloombooks/comical-js/llms.txt A static factory method that returns a default BubbleSpec for a given HTML element and bubble style. It automatically configures tails for styles that use them and sets default appearances for others. ```APIDOC ## Bubble.getDefaultBubbleSpec(element: HTMLElement, style?: string): BubbleSpec ### Description Static factory that returns a sensible default `BubbleSpec` for the given element and bubble style. For styles with tails (e.g., "speech", "shout", "thought"), a default tail pointing away from the element is calculated automatically. For "caption" style, a gradient background and shadow offset are preset. For "none", a transparent background is used. ### Method `Bubble.getDefaultBubbleSpec` ### Parameters #### Path Parameters - **element** (HTMLElement) - Required - The HTML element to generate the spec for. - **style** (string) - Optional - The desired bubble style (e.g., "speech", "caption"). ### Response #### Success Response (BubbleSpec) - **version** (string) - The version of the bubble spec. - **style** (string) - The style of the bubble. - **tails** (Array) - An array of tail configurations. - **level** (number) - The z-level for layering. - **backgroundColors** (Array) - Optional - Background colors for the bubble. - **shadowOffset** (number) - Optional - Offset for the shadow effect. ### Request Example ```typescript import { Bubble } from "comicaljs"; const textDiv = document.getElementById("bubble-text") as HTMLElement; // Get a default speech bubble spec (automatically places the tail) const speechSpec = Bubble.getDefaultBubbleSpec(textDiv, "speech"); console.log(speechSpec); // { version: "1.0", style: "speech", tails: [{ tipX: 250, tipY: 300, midpointX: 200, midpointY: 220, autoCurve: true }], level: 1 } // A caption with no tail gets preset colors and shadow const captionSpec = Bubble.getDefaultBubbleSpec(textDiv, "caption"); // { style: "caption", backgroundColors: ["#FFFFFF", "#DFB28B"], shadowOffset: 5, tails: [] } // Apply the spec: Bubble.setBubbleSpec(textDiv, speechSpec); ``` ``` -------------------------------- ### Comical.setActiveBubbleListener(listener: ((selected: HTMLElement | undefined) => void) | undefined): void Source: https://context7.com/bloombooks/comical-js/llms.txt Registers a callback that fires whenever the active bubble changes. The callback receives the content `HTMLElement` of the newly active bubble, or `undefined` when no bubble is active. This is useful for synchronizing external UI elements with the selected bubble. ```APIDOC ## `Comical.setActiveBubbleListener(listener: ((selected: HTMLElement | undefined) => void) | undefined): void` ### Description Registers a callback that fires whenever the active bubble changes. The callback receives the content `HTMLElement` of the newly active bubble, or `undefined` when no bubble is active. Use this to keep external UI (e.g., a style picker toolbar) in sync with the selected bubble. ### Method `setActiveBubbleListener` ### Parameters #### Path Parameters - **listener** ((selected: HTMLElement | undefined) => void) | undefined - Required - The callback function to register. It receives the active element or undefined. ### Request Example ```typescript import { Comical } from "comicaljs"; const styleLabel = document.getElementById("current-style-label")!; // Register a listener before starting to edit Comical.setActiveBubbleListener((activeElement) => { if (activeElement) { const spec = JSON.parse( activeElement.getAttribute("data-bubble")!.replace(/`/g, '"') ); styleLabel.textContent = `Active bubble style: ${spec.style}`; } else { styleLabel.textContent = "No bubble selected"; } }); const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); ``` ``` -------------------------------- ### bubble.mergeWithNewBubbleProps Source: https://context7.com/bloombooks/comical-js/llms.txt Intelligently merges partial BubbleSpec updates into the existing spec, preserving user customizations while allowing for style changes and property updates. ```APIDOC ## bubble.mergeWithNewBubbleProps(newProps: BubbleSpecPattern): void ### Description Intelligently merges a partial spec update into the bubble's existing spec, preserving user-set properties (like tail position) while resetting properties that were at their style defaults when the style changes. This is the preferred way to change a bubble's style or individual properties without blowing away customizations. ### Method `bubble.mergeWithNewBubbleProps` ### Parameters #### Path Parameters - **newProps** (BubbleSpecPattern) - Required - A partial object containing the properties to update. - **style** (string) - Optional - The new style for the bubble. - **backgroundColors** (Array) - Optional - New background colors. - **cornerRadiusX** (number) - Optional - The X-axis corner radius. - **cornerRadiusY** (number) - Optional - The Y-axis corner radius. ### Request Example ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("bubble-text") as HTMLElement; const bubble = new Bubble(textDiv); Comical.startEditing([container]); // Switch to thought style — tail position is preserved if user moved it, // background color resets to thought-style default bubble.mergeWithNewBubbleProps({ style: "thought" }); Comical.update(container); // Just change the background color, keep everything else bubble.mergeWithNewBubbleProps({ backgroundColors: ["#1d94a4"] }); Comical.update(container); // Add rounded corners to a caption or rectangle bubble.mergeWithNewBubbleProps({ cornerRadiusX: 8, cornerRadiusY: 8 }); Comical.update(container); ``` ``` -------------------------------- ### Allow Midpoint Handle Overlap with CSS Selector Source: https://context7.com/bloombooks/comical-js/llms.txt Configure Comical.js to allow tail midpoint handles to enter specific bubble containers. Use this to permit handles to pass through overlay images or caption boxes. ```typescript import { Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); // Allow midpoints to overlap elements with class "overlay-image" or "transparent-caption" Comical.setSelectorForBubblesWhichTailMidpointMayOverlap(".overlay-image, .transparent-caption"); ``` -------------------------------- ### Activate Specific Bubble Element Source: https://context7.com/bloombooks/comical-js/llms.txt Makes a bubble active, showing its tail drag handles. Only one bubble can be active at a time. Pass `undefined` to deactivate all. ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv1 = document.getElementById("bubble-1") as HTMLElement; const textDiv2 = document.getElementById("bubble-2") as HTMLElement; Comical.startEditing([container]); // Show handles for bubble-1 (e.g., when a user clicks the text element) textDiv1.addEventListener("click", () => { Comical.activateElement(textDiv1); }); textDiv2.addEventListener("click", () => { Comical.activateElement(textDiv2); }); // Deactivate all handles (e.g., clicking the background) container.addEventListener("click", (e) => { if (e.target === container) { Comical.activateElement(undefined); } }); ``` -------------------------------- ### Comical.getBubbleHit Source: https://context7.com/bloombooks/comical-js/llms.txt Retrieves the highest-level Bubble object hit by a point (x, y) within a container, considering visibility and optional exclusions. ```APIDOC ## Comical.getBubbleHit(parentContainer, x, y, onlyIfEnabled?, ignoreSelector?): Bubble | undefined ### Description Returns the highest-level `Bubble` whose shape is hit by the point `(x, y)`, or `undefined` if nothing is hit. Bubbles are sorted by descending `level`, so higher-level bubbles take priority. Invisible bubbles (`display: none`) are always excluded. Pass `onlyIfEnabled: true` to skip bubbles whose content has `pointer-events: none`. Pass an `ignoreSelector` CSS selector to exclude matching bubbles. ### Method `Comical.getBubbleHit` ### Parameters #### Path Parameters - **parentContainer** (HTMLElement) - The container element. - **x** (number) - The x-coordinate in real canvas pixels. - **y** (number) - The y-coordinate in real canvas pixels. - **onlyIfEnabled** (boolean, Optional) - If true, skips bubbles with `pointer-events: none`. - **ignoreSelector** (string, Optional) - A CSS selector for bubbles to ignore. ### Request Example ```typescript import { Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); container.addEventListener("click", (e: MouseEvent) => { const bubble = Comical.getBubbleHit(container, e.offsetX, e.offsetY); if (bubble) { console.log("Clicked bubble style:", bubble.getStyle()); Comical.activateBubble(bubble); } }); // Ignoring bubbles marked with a specific class: const hitBubble = Comical.getBubbleHit(container, 100, 200, false, ".overlay-image"); ``` ### Response #### Success Response (Bubble | undefined) - **Bubble**: The highest-level `Bubble` object hit by the point. - **undefined**: If no bubble is hit. ``` -------------------------------- ### Stop Comical Editing Mode Source: https://context7.com/bloombooks/comical-js/llms.txt Ends editing mode for all active containers, converting canvases to static SVGs. Subsequent calls to startEditing can restore editing. ```typescript import { Comical } from "comicaljs"; const saveButton = document.getElementById("save-btn")!; saveButton.addEventListener("click", () => { // Converts all active canvases to static SVG elements Comical.stopEditing(); // The DOM now contains for each container // No JavaScript needed to render the bubbles in the saved HTML }); const editButton = document.getElementById("edit-btn")!; editButton.addEventListener("click", () => { const container = document.getElementById("image-container") as HTMLElement; // Re-activates editing; reads data-bubble attributes to reconstruct canvas Comical.startEditing([container]); }); ``` -------------------------------- ### Comical.setSelectorForBubblesWhichTailMidpointMayOverlap Source: https://context7.com/bloombooks/comical-js/llms.txt Sets a CSS selector to identify bubble containers where tail midpoint handles are allowed to enter. This is useful for allowing handles to pass through overlay images or caption boxes. ```APIDOC ## Comical.setSelectorForBubblesWhichTailMidpointMayOverlap(selector: string): void ### Description Sets a CSS selector that identifies bubble containers whose interiors a tail's midpoint control handle is allowed to enter. By default, midpoint handles cannot be dragged inside any bubble. Use this to allow overlay images or caption boxes to be "passed through" by midpoint handles. ### Method `Comical.setSelectorForBubblesWhichTailMidpointMayOverlap` ### Parameters #### Path Parameters - **selector** (string) - Required - A CSS selector string. ### Request Example ```typescript import { Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); // Allow midpoints to overlap elements with class "overlay-image" or "transparent-caption" Comical.setSelectorForBubblesWhichTailMidpointMayOverlap(".overlay-image, .transparent-caption"); ``` ``` -------------------------------- ### Comical.update(container: HTMLElement): void Source: https://context7.com/bloombooks/comical-js/llms.txt Re-renders all bubbles in an already-active container after programmatic changes to the DOM or bubble specs. This method should be called after modifying bubble elements or their properties to ensure the canvas reflects the latest changes. ```APIDOC ## `Comical.update(container: HTMLElement): void` ### Description Re-renders all bubbles in an already-active container after programmatic changes to the DOM or bubble specs. Must be called after adding or removing child elements that have `data-bubble` attributes, or after programmatically changing a bubble's spec via `bubble.mergeWithNewBubbleProps()` or `bubble.setBubbleSpec()`. ### Method `update` ### Parameters #### Path Parameters - **container** (HTMLElement) - Required - The HTML element acting as the container for the bubbles that needs to be re-rendered. ### Request Example ```typescript import { Comical, Bubble } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("speech-text") as HTMLElement; Comical.startEditing([container]); // Change the bubble style dynamically const bubble = new Bubble(textDiv); bubble.mergeWithNewBubbleProps({ style: "thought" }); // Bubble spec is persisted to data-bubble attribute; now redraw the canvas: Comical.update(container); // The canvas now shows a thought bubble instead of the previous style ``` ``` -------------------------------- ### Set Bubble Specification and Persist to Element Source: https://context7.com/bloombooks/comical-js/llms.txt Apply a full BubbleSpec to a bubble element and save it to the 'data-bubble' attribute. Ensure Comical editing is updated if active. ```typescript import { Bubble, Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; const textDiv = document.getElementById("bubble-text") as HTMLElement; const bubble = new Bubble(textDiv); bubble.setBubbleSpec({ version: "1.0", style: "shout", tails: [{ tipX: 420, tipY: 400, midpointX: 320, midpointY: 375, autoCurve: true }], level: 2, backgroundColors: ["rgba(255,230,0,0.85)"], outerBorderColor: "red", shadowOffset: 4 }); // If editing is already active, redraw: Comical.update(container); ``` -------------------------------- ### Comical.somethingHit Source: https://context7.com/bloombooks/comical-js/llms.txt Determines if a point (x, y) intersects with any Comical-drawn paper.js item within the specified container. ```APIDOC ## Comical.somethingHit(element: HTMLElement, x: number, y: number): boolean ### Description Returns `true` if the point `(x, y)` (real canvas pixels) hits any paper.js item drawn by Comical in the given container—any bubble shape, tail shape, or handle. Useful for routing click events between Comical's canvas layer and other DOM elements. ### Method `Comical.somethingHit` ### Parameters #### Path Parameters - **element** (HTMLElement) - The container element. - **x** (number) - The x-coordinate in real canvas pixels. - **y** (number) - The y-coordinate in real canvas pixels. ### Request Example ```typescript import { Comical } from "comicaljs"; const container = document.getElementById("image-container") as HTMLElement; Comical.startEditing([container]); container.addEventListener("click", (e: MouseEvent) => { const hitComical = Comical.somethingHit(container, e.offsetX, e.offsetY); if (!hitComical) { console.log("Background clicked — deselecting bubbles"); Comical.activateElement(undefined); } }); ``` ### Response #### Success Response (boolean) - **true**: If the point hits a Comical item. - **false**: Otherwise. ``` -------------------------------- ### Comical.convertCanvasToSvgImg Source: https://context7.com/bloombooks/comical-js/llms.txt Converts the active editing canvas of a single specific parent container to a static SVG, deactivating editing for just that container. Unlike `stopEditing()`, other active containers are unaffected. ```APIDOC ## Comical.convertCanvasToSvgImg(parent: HTMLElement): void ### Description Converts the active editing canvas of a single specific parent container to a static SVG, deactivating editing for just that container. Unlike `stopEditing()`, other active containers are unaffected. ### Method `Comical.convertCanvasToSvgImg` ### Parameters #### Path Parameters - **parent** (HTMLElement) - Required - The parent HTML element whose canvas should be converted to SVG. ```