<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>CSS Script</title>
	<atom:link href="https://www.cssscript.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cssscript.com/</link>
	<description>Best Free JavaScript &#38; CSS/CSS3 Libraries For Modern Web Design</description>
	<lastBuildDate>Tue, 21 Apr 2026 15:05:25 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://i0.wp.com/www.cssscript.com/wp-content/uploads/2016/10/cropped-web-code.png?fit=32%2C32&#038;ssl=1</url>
	<title>CSS Script</title>
	<link>https://www.cssscript.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">61228280</site>	<item>
		<title>Handle Long Press/Tap Event In JavaScript  &#8211; long-press-event</title>
		<link>https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/</link>
					<comments>https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/#comments</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 14:50:41 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">https://cssscript.com/?p=6392</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="long-press" style="float:left; margin:0 15px 15px 0;" decoding="async" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="(max-width: 150px) 100vw, 150px" />long-press.js is a small JavaScript library which detects and handles the long press/tap event on a specific DOM (or the whole document). Supports both desktop and mobile.</p>
<p>The post <a href="https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/">Handle Long Press/Tap Event In JavaScript  &#8211; long-press-event</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="long-press" style="float:left; margin:0 15px 15px 0;" decoding="async" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2018/02/long-press.png?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="(max-width: 150px) 100vw, 150px" /><p>long-press.js is a small JavaScript library which detects and handles the long press/tap event on a specific DOM (or the whole document). Supports both desktop and mobile.</p>
<h2>How to use it:</h2>
<p>Import the long-press.js library into your html document.</p>
<pre class="brush:xml">&lt;script src="dist/long-press.min.js"&gt;&lt;/script&gt;</pre>
<p>Attach the long press/tap event handler to a specific element.</p>
<pre class="brush:javascript">var el = document.getElementById('demo');

el.addEventListener('long-press', function(e) {
  // do something
});</pre>
<p>Or attach the event handler to the whole document.</p>
<pre class="brush:javascript">document.addEventListener('long-press', function(e) {
  // do something
});</pre>
<p>Customize the time delay in milliseconds. Default: 1.5s.</p>
<pre class="brush:xml">&lt;div id="demo" data-long-press-delay="500"&gt;Press and hold me for .5s&lt;/div&gt;</pre>
<h2>Changelog:</h2>
<p>v2.5.2 (04/21/2026)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.5.0 (04/27/2024)</p>
<ul>
<li>Bugfix</li>
</ul>
<p>v2.4.6 (02/25/2023)</p>
<ul>
<li>Bugfix</li>
</ul>
<p>v2.4.4 (05/25/2021)</p>
<ul>
<li>update</li>
</ul>
<p>v2.4.2 (11/16/2020)</p>
<ul>
<li>suppress click event bug</li>
</ul>
<p>v2.4.1 (11/15/2020)</p>
<ul>
<li>Click event no longer fires after long-press event</li>
</ul>
<p>v2.1.0 (09/23/2019)</p>
<ul>
<li>Added possibility to forward clientX and clientY values</li>
</ul>
<p>v2.0.1 (09/08/2019)</p>
<ul>
<li>removed disable context menu, causes problems on android devices</li>
</ul>
<p>v2.0.0 (08/29/2019)</p>
<ul>
<li>replaced setTimeout with requestAnimationFrame for more accurate timing</li>
</ul>
<p>v1.0.7 (08/28/2019)</p>
<ul>
<li>disabled logging</li>
</ul>
<p>v1.0.6 (07/02/2019)</p>
<ul>
<li>refactored code and added useCapture=true for events</li>
</ul>
<p>03/21/2018</p>
<ul>
<li>fixed IIFE Webpack bug</li>
</ul>
<p>02/02/2018</p>
<ul>
<li>renamed to long-press-event</li>
</ul>
<p>09/14/2018</p>
<ul>
<li>v1.0.2</li>
</ul>
<p>The post <a href="https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/">Handle Long Press/Tap Event In JavaScript  &#8211; long-press-event</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/handle-long-press-tap-event-in-javascript-long-press-js/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">6392</post-id>	</item>
		<item>
		<title>2D/3D Data Visualization with Ray Tracing in JavaScript &#8211; MorphCharts</title>
		<link>https://www.cssscript.com/data-visualization-morphcharts/</link>
					<comments>https://www.cssscript.com/data-visualization-morphcharts/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 14:50:08 +0000</pubDate>
				<category><![CDATA[Chart & Graph]]></category>
		<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=100905</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="data-visualization-morphcharts" style="float:left; margin:0 15px 15px 0;" decoding="async" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="(max-width: 150px) 100vw, 150px" />Microsoft's MorphCharts JavaScript library creates 3D data visualizations with WebGPU, ray tracing, and Vega-inspired JSON specs for immersive data experiences.</p>
<p>The post <a href="https://www.cssscript.com/data-visualization-morphcharts/">2D/3D Data Visualization with Ray Tracing in JavaScript &#8211; MorphCharts</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="data-visualization-morphcharts" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/10/data-visualization-morphcharts.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>MorphCharts is an open-source JavaScript visualization library that creates rich 2D and 3D data visualizations using ray tracing and procedural geometry. Developed by Microsoft.</p>
<p>The library takes a dual approach to chart creation. You can define visualizations using a JSON specification based on an extended subset of the VEGA grammar, or you can build them programmatically using the Core library with a Renderer.</p>
<p>This means you can prototype quickly with JSON specs or integrate deeply into your application&#8217;s architecture with code-based implementations.</p>
<h2>Features:</h2>
<ul>
<li><strong>Ray-traced rendering pipeline</strong>: Renders charts using path tracing with global illumination, supporting diffuse, metal, glass, glossy, and emissive materials for photorealistic visualizations.</li>
<li><strong>Multiple geometric primitives</strong>: Supports rectangles, cuboids, spheres, cylinders, hex prisms, ring segments, and torus segments for diverse visualization types.</li>
<li><strong>Flexible data sources</strong>: Load data inline through JSON definitions or link to external CSV files via URLs, with local file support in the web client.</li>
<li><strong>WebGPU acceleration</strong>: Uses GPU rendering through WebGPU for fast ray tracing and multiple render passes, including color, normal maps, depth maps, and edge detection.</li>
<li><strong>Arbitrary resolution output</strong>: Generate visualizations at any resolution, including 4K single images or larger outputs through tiled rendering that can be stitched together.</li>
<li><strong>Material and texture support</strong>: Apply solid colors or textures to any geometric primitive with full material property control for realistic surface appearances.</li>
</ul>
<h2>Installation</h2>
<p>1. Clone the repository and install dependencies:</p>
<pre>git clone https://github.com/microsoft/morphcharts.git
cd morphcharts
npm install</pre>
<p>2. Build and start the client. The build process compiles all components and starts a local web server. The <code>start_client</code> command automatically opens <code>client.html</code> in your default browser, where you can immediately start creating visualizations through the web interface.</p>
<pre>npm run build_client
npm run start_client</pre>
<p>3. Create Visualizations with JSON Specifications. This approach extends VEGA grammar with 3D support. Here&#8217;s a 3D bar chart example:</p>
<pre>{
  "title": "3D Stacked Bar Chart",
  "description": "A 3D stacked bar chart",
  "width": 640,
  "height": 360,
    
  "camera": {"position": [0, 0.6, 1.2]},
    
  "data": [ 
    {
      "name": "table",
      "values": [
        {"category": "A", "subcategory": "One", "amount": 42},
        {"category": "B", "subcategory": "One", "amount": 87},
        {"category": "C", "subcategory": "One", "amount": 15},
        {"category": "D", "subcategory": "One", "amount": 63},
        {"category": "E", "subcategory": "One", "amount": 29},
        {"category": "F", "subcategory": "One", "amount": 91},
        {"category": "G", "subcategory": "One", "amount": 8},
        {"category": "H", "subcategory": "One", "amount": 76},
        {"category": "I", "subcategory": "One", "amount": 54},
        {"category": "J", "subcategory": "One", "amount": 33},

        {"category": "A", "subcategory": "Two", "amount": 67},
        {"category": "B", "subcategory": "Two", "amount": 23},
        {"category": "C", "subcategory": "Two", "amount": 94},
        {"category": "D", "subcategory": "Two", "amount": 38},
        {"category": "E", "subcategory": "Two", "amount": 81},
        {"category": "F", "subcategory": "Two", "amount": 12},
        {"category": "G", "subcategory": "Two", "amount": 56},
        {"category": "H", "subcategory": "Two", "amount": 33},
        {"category": "I", "subcategory": "Two", "amount": 70},
        {"category": "J", "subcategory": "Two", "amount": 19},

        {"category": "A", "subcategory": "Three", "amount": 25},
        {"category": "B", "subcategory": "Three", "amount": 59},
        {"category": "C", "subcategory": "Three", "amount": 11},
        {"category": "D", "subcategory": "Three", "amount": 72},
        {"category": "E", "subcategory": "Three", "amount": 44},
        {"category": "F", "subcategory": "Three", "amount": 60},
        {"category": "G", "subcategory": "Three", "amount": 39},
        {"category": "H", "subcategory": "Three", "amount": 90},
        {"category": "I", "subcategory": "Three", "amount": 18},
        {"category": "J", "subcategory": "Three", "amount": 77}
      ],
      "transform": [
        {
          "type": "stack",
          "groupby": ["category"],
          "sort": {"field": "subcategory"},
          "field": "amount",
          "as": ["y0", "y1"]
        }
      ]
    },
    {
      "name": "aggregate",
      "source": "table",
      "transform": [
        {
          "type": "aggregate",
          "groupby": ["category"],
          "ops": ["sum"],
          "fields": ["amount"],
          "as": ["total"]
        }
      ]
    }
  ],

  "scales": [
    {
      "name": "xscale", 
      "type": "band", 
      "_domain": {"data": "table", "field": "category"},
      "domain": {
        "data": "aggregate",
        "field": "category",
        "sort": {"field": "total", "op": "max", "order": "ascending"}
      },
      "range": "width",
      "padding": 0.25
    }, 
    {
      "name": "yscale",
      "type": "linear",
      "domain": {"data": "table", "field": "y1"},
      "nice": true,
      "range": "height"
    },
    {
      "name": "color",
      "type": "ordinal",
      "range": "category",
      "domain": {"data": "table", "field": "subcategory"}
    }
  ],

  "axes": [
    {
      "orient": "bottom",
      "scale": "xscale",
      "labelBaseline": "top",
      "labelOffsetY": 0.1,
      "labelOffsetZ": 32,
      "labelAngleX": 90,
      "title": "Category",
      "titleOffsetZ": 64,
      "titleOffsetY": 0.1,
      "titleAngleX": 90,
      "domain": false
    },
    {
      "orient": "left",
      "orientZ": "back",
      "scale": "yscale",
      "grid": true,
      "gridWidth": 0.1,
      "tickCount": 10,
      "labelAlign": "right",
      "labelOffsetX": -2,
      "labelOffsetY": 4,
      "title": "Amount",
      "titleOffsetX": -32,
      "titleAngle": -90
    }
  ],
 
  "marks": [
    {
      "type": "rect",
      "geometry": "xzrect",
      "material": "glossy",
      "encode": {
        "enter": {
          "xc": {"signal": "width/2"},
          "width": {"signal": "width*2"},
          "depth": {"signal": "width*2"},
          "fuzz": {"value": 0.1},
          "fill": {"value": "white"}
        }
      }
    },
    {
      "type": "rect",
      "geometry": "cylindersdf",
      "material": "glossy",
      "from": {"data": "table"},
      "encode": {
        "enter": {
          "x": {"scale": "xscale", "field": "category"},
          "width": {"scale": "xscale", "band": 1},
          "y": {"scale": "yscale", "field": "y0"},
          "y2": {"scale": "yscale", "field": "y1"},
          "rounding": {"scale": "xscale", "band": 0.025},
          "fill": {"scale": "color", "field": "subcategory"}
        }
      }
    },
    {
      "type": "text",
      "from": {"data": "table"},
      "encode": {
        "enter": {
          "align": {"value": "center"},
          "baseline": {"value": "middle"},
          "x": {"scale": "xscale", "field": "category", "band": 0.5},
          "y": {"scale": "yscale", "signal": "(datum.y0+datum.y1)/2"},
          "z": {"scale": "xscale", "band": 0.5},
          "fill": {"value": "white"},
          "text": {"field": "amount"}
        }
      }
    },
    {
      "type": "text",
      "from": {"data": "aggregate"},
      "encode": {
        "enter": {
          "align": {"value": "center"},
          "baseline": {"value": "bottom"},
          "x": {"scale": "xscale", "field": "category", "band": 0.5},
          "y": {"scale": "yscale", "field": "total"},
          "stroke": {"value": "white"},
          "strokeWidth": {"value": 0.5},
          "text": {"field": "total"}
        }
      }
    }
  ]
}</pre>
<h2>Changelog:</h2>
<p>04/21/2026</p>
<ul>
<li>Update</li>
</ul>
<p>The post <a href="https://www.cssscript.com/data-visualization-morphcharts/">2D/3D Data Visualization with Ray Tracing in JavaScript &#8211; MorphCharts</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/data-visualization-morphcharts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">100905</post-id>	</item>
		<item>
		<title>5,600+ Brand SVG Icons for Developers &#8211; theSVG</title>
		<link>https://www.cssscript.com/brand-svg-icons/</link>
					<comments>https://www.cssscript.com/brand-svg-icons/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 07:00:54 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Icon]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=104507</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="brand-svg-icons" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />5,600+ brand SVG icons as typed React, Vue, and Svelte components with CDN, SVG/PNG, CLI, and MCP server support.</p>
<p>The post <a href="https://www.cssscript.com/brand-svg-icons/">5,600+ Brand SVG Icons for Developers &#8211; theSVG</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="brand-svg-icons" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/03/brand-svg-icons.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>theSVG is an open-source icon library that provides 5,600+ brand SVG icons as React, Vue, and Svelte components, inline CDN assets, or downloadable PNG files.</p>
<h2>Features:</h2>
<ul>
<li>4,000+ brand icons: AI tools, dev frameworks, social platforms, payment processors, cloud services, databases, and 55+ other categories.</li>
<li>Each icon supports up to seven variants: <code>default</code>, <code>mono</code>, <code>light</code>, <code>dark</code>, <code>wordmark</code>, <code>wordmarkLight</code>, and <code>wordmarkDark</code>.</li>
<li>The <code>@thesvg/icons</code> scoped package lets you import a single icon.</li>
<li>Dedicated typed component packages exist for React, Vue 3, and Svelte</li>
<li>A public HTTP API at <code>thesvg.org</code> exposes search, single-icon metadata, category listing, and raw SVG file delivery.</li>
<li><code>@thesvg/cli</code> lets you pull icons into your project from the terminal.</li>
<li><code>@thesvg/mcp-server</code> integrates theSVG into Claude, Cursor, and Windsurf AI assistants.</li>
</ul>
<h2>Installation:</h2>
<p>1. Install the full <code>thesvg</code> package:</p>
<pre># NPM
$ npm install thesvg</pre>
<p>2. For a tree-shakeable setup where you import only specific icons, use the scoped package:</p>
<pre># NPM
$ npm install @thesvg/icons
</pre>
<p>3. For framework-specific typed components, install the relevant adapter:</p>
<pre># React
npm install @thesvg/react

# Vue 3
npm install @thesvg/vue

# Svelte
npm install @thesvg/svelte</pre>
<h2>Usages:</h2>
<p>1. The <code>thesvg</code> package exposes icon objects directly. Each object contains the raw SVG string, the brand title, the primary hex color, and all available variants.</p>
<pre>// Import a single icon object from the main package
import vercel from "thesvg/vercel";

// Raw SVG markup string
console.log(vercel.svg);

// Human-readable brand name: "Vercel"
console.log(vercel.title);

// Primary brand hex code (no leading #): "000000"
console.log(vercel.hex);

// Object of all available SVG variants for this icon
// Variants: default, mono, light, dark, wordmark, etc.
console.log(vercel.variants);</pre>
<p>2. Use the icons as React components.</p>
<pre>// Import typed React icon components
import { Figma, Notion, Linear } from "@thesvg/react";
export function TechStack() {
  return (
    &lt;div className="flex gap-4 items-center"&gt;
      {/* Render the Figma logo at 32px with a custom CSS class */}
      &lt;Figma width={32} height={32} className="text-gray-800" /&gt;
      {/* Render the Notion logo in the same row */}
      &lt;Notion width={32} height={32} className="text-gray-800" /&gt;
      {/* Render the Linear logo in the same row */}
      &lt;Linear width={32} height={32} className="text-gray-800" /&gt;
    &lt;/div&gt;
  );
}</pre>
<p>3. Use the icons as Vue components.</p>
<pre>&lt;script setup lang="ts"&gt;
// Import typed Vue components
import { Stripe, Supabase } from "@thesvg/vue";
&lt;/script&gt;
&lt;template&gt;
  &lt;div class="logo-row"&gt;
    &lt;!-- Render the Stripe logo at 40px wide --&gt;
    &lt;Stripe width="40" height="40" /&gt;
    &lt;!-- Render the Supabase logo next to it --&gt;
    &lt;Supabase width="40" height="40" /&gt;
  &lt;/div&gt;
&lt;/template&gt;</pre>
<p>4. Use the icons as Svelte components.</p>
<pre>&lt;script&gt;
  // Import Svelte icon components from the dedicated adapter
  import { Tailwind, Prisma } from "@thesvg/svelte";
&lt;/script&gt;
&lt;!-- Render both brand icons at 28px --&gt;
&lt;Tailwind width="28" height="28" /&gt;
&lt;Prisma width="28" height="28" /&gt;</pre>
<p>5. Or drop the icons directly into any HTML file via the thesvg.org CDN or jsDelivr.</p>
<pre>&lt;!-- URL pattern: https://thesvg.org/icons/{slug}/{variant}.svg --&gt;
&lt;!-- Default (full color) version of the Vercel logo --&gt;
&lt;img
  src="https://thesvg.org/icons/vercel/default.svg"
  width="32"
  height="32"
  alt="Vercel"
/&gt;
&lt;!-- Mono variant — inherits the current text color via CSS --&gt;
&lt;img
  src="https://thesvg.org/icons/vercel/mono.svg"
  width="32"
  height="32"
  alt="Vercel"
  style="color: #6366f1;"
/&gt;
&lt;!-- Wordmark variant — full horizontal logo lockup --&gt;
&lt;img
  src="https://thesvg.org/icons/vercel/wordmark.svg"
  width="120"
  height="32"
  alt="Vercel Wordmark"
/&gt;</pre>
<pre>&lt;img
  src="https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/stripe/default.svg"
  width="32"
  height="32"
  alt="Stripe"
/&gt;</pre>
<p>6. Use the <code>@thesvg/cli</code> tool to pull icons directly into your project directory.</p>
<pre># Add the Vercel icon to your project
npx @thesvg/cli add vercel

# Search icons by keyword — useful for browsing the AI category
npx @thesvg/cli search "payment"</pre>
<p>7. The public API at <code>https://thesvg.org</code> supports icon search, metadata retrieval, category listing, and raw SVG delivery.</p>
<pre>GET /api/icons?q={query}&amp;category={category}&amp;limit={n}
  — Search the icon library by keyword and optional category filter.

GET /api/icons/{slug}
  — Fetch full metadata for a single icon, including all variants.

GET /api/categories
  — List all 55+ categories with icon counts.

GET /api/registry/{slug}
  — shadcn-compatible registry endpoint for direct component consumption.

GET /icons/{slug}/{variant}.svg
  — Serve a raw SVG file for a specific icon and variant.</pre>
<pre># Search for icons in the "Payment" category
curl "https://thesvg.org/api/icons?category=Payment&amp;limit=10"

# Get full metadata for the Stripe icon
curl "https://thesvg.org/api/icons/stripe"

# List all available categories
curl "https://thesvg.org/api/categories"</pre>
<h2>Icon Variants:</h2>
<table>
<thead>
<tr>
<th>Variant</th>
<th>Key</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Default</td>
<td><code>default</code></td>
<td>Primary brand color. Always present.</td>
</tr>
<tr>
<td>Mono</td>
<td><code>mono</code></td>
<td>Inherits the current text color.</td>
</tr>
<tr>
<td>Light</td>
<td><code>light</code></td>
<td>White fill — for dark backgrounds.</td>
</tr>
<tr>
<td>Dark</td>
<td><code>dark</code></td>
<td>Black fill — for light backgrounds.</td>
</tr>
<tr>
<td>Wordmark</td>
<td><code>wordmark</code></td>
<td>Full horizontal logo with text.</td>
</tr>
<tr>
<td>Wordmark Light</td>
<td><code>wordmarkLight</code></td>
<td>White wordmark version.</td>
</tr>
<tr>
<td>Wordmark Dark</td>
<td><code>wordmarkDark</code></td>
<td>Dark wordmark version.</td>
</tr>
</tbody>
</table>
<h2>MCP server for AI assistants:</h2>
<p>The <code>@thesvg/mcp-server</code> package connects theSVG to AI-powered editors. Configure it in your Claude, Cursor, or Windsurf workspace to let your AI assistant search and insert brand icons on demand.</p>
<pre><code class="language-bash"># Install the MCP server globally
npm install -g @thesvg/mcp-server
</code></pre>
<h2>FAQs:</h2>
<p><strong>Q: Can I use the brand icons in a commercial project?</strong><br />
A: The theSVG codebase and tooling are MIT-licensed. The brand icons themselves remain the intellectual property of their respective trademark holders. Check each brand&#8217;s trademark usage guidelines before commercial use.</p>
<p><strong>Q: The CDN URL returns a 404 for the variant I requested. What&#8217;s wrong?</strong><br />
A: Not every icon ships with all seven variants. The <code>default</code> variant is the only one guaranteed to exist for every icon. Query the REST API at <code>GET /api/icons/{slug}</code> first to confirm which variants are available for a given slug, then reference only those in your markup.</p>
<p><strong>Q: How do I get the correct hex color to style the mono icon?</strong><br />
A: Import the icon object from <code>thesvg/{slug}</code> and read the <code>.hex</code> property. It returns the brand&#8217;s primary color as a six-digit hex string.</p>
<p><strong>Q: My bundle includes icons I&#8217;m not using. How do I fix that?</strong><br />
A: Switch from the <code>thesvg</code> package to <code>@thesvg/icons</code>. The scoped package is designed for tree-shaking. Import individual icons by path (<code>import { Vercel } from "@thesvg/icons/vercel"</code>) so your bundler can drop everything else at build time.</p>
<p><strong>Q: How do I integrate theSVG into a Next.js App Router project?</strong><br />
A: Install <code>@thesvg/react</code> and import components inside a Client Component or a Server Component. The components are pure SVG output with no client-side interactivity, so they work in both contexts. For image-based usage in Next.js, use the CDN URL with the built-in <code>&lt;Image&gt;</code> component and set <code>unoptimized={true}</code> if you need the raw SVG format.</p>
<h2>Changelog:</h2>
<p>v2.2.0 (04/21/2026)</p>
<ul>
<li>Added more icons.</li>
</ul>
<p>v2.1.1 (03/29/2026)</p>
<ul>
<li>Bugfixes and improvements.</li>
</ul>
<p>v2.1.0 (03/26/2026)</p>
<ul>
<li>New collection, new integration, and community-submitted icons.</li>
</ul>
<p>v2.0.0 (03/18/2026)</p>
<ul>
<li>Added 739 AWS Architecture Icons</li>
</ul>
<p>The post <a href="https://www.cssscript.com/brand-svg-icons/">5,600+ Brand SVG Icons for Developers &#8211; theSVG</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/brand-svg-icons/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">104507</post-id>	</item>
		<item>
		<title>Minimal WYSIWYG Editor In Pure JavaScript &#8211; Suneditor</title>
		<link>https://www.cssscript.com/minimal-wysiwyg-editor-pure-javascript-suneditor/</link>
					<comments>https://www.cssscript.com/minimal-wysiwyg-editor-pure-javascript-suneditor/#comments</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 07:00:51 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Recommended]]></category>
		<category><![CDATA[Text]]></category>
		<category><![CDATA[editor]]></category>
		<category><![CDATA[WYSIWYG Editor]]></category>
		<guid isPermaLink="false">http://cssscript.com/?p=4683</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="SunEditor" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />Suneditor is a lightweight, flexible, customizable, pure JavaScript WYSIWYG text editor for your web applications.</p>
<p>The post <a href="https://www.cssscript.com/minimal-wysiwyg-editor-pure-javascript-suneditor/">Minimal WYSIWYG Editor In Pure JavaScript &#8211; Suneditor</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="SunEditor" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2017/05/SunEditor.png?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>Suneditor is a lightweight, flexible, customizable, pure JavaScript WYSIWYG text editor for your web applications.</p>
<h2>How to use it:</h2>
<p>Load the needed suneditor.css and suneditor.js in the html document.</p>
<pre class="brush:xml">&lt;link href="suneditor/css/suneditor.css" rel="stylesheet"&gt;
&lt;script src="suneditor/js/suneditor.js"&gt;&lt;/script&gt;</pre>
<p>Create a normal textarea element for the WYSIWYG editor.</p>
<pre class="brush:xml">&lt;textarea id="editor"&gt;Hello World!&lt;/textarea&gt;</pre>
<p>Create a new editor from the textarea element. Done.</p>
<pre class="brush:javascript">SUNEDITOR.create('editor');</pre>
<p>All default options to customize the WYSIWYG editor.</p>
<pre class="brush:javascript">SUNEDITOR.create('editor', {

  // plugins to load
  plugins: [
    font,
    fontSize,
    formatBlock,
    fontColor,
    hiliteColor,
    align,
    lineHeight,
    horizontalRule,
    list,
    table,
    link,
    image,
    video,
    template,
    textStyle,
    blockquote,
    paragraphStyle,
    math, // You must add the 'katex' library at options to use the 'math' plugin.
    imageGallery
  ] 

  // set the initial value
  value: '',

  // disable clean mode, which checks the styles, classes, etc. of the editor content
  strictMode : false,

  // Enforces strict HTML validation based on the editor`s policy. 
  // Applies to methods like setContents to ensure content compliance when enabled. 
  strictHTMLValidation: true,

  // default tag name of the editor.
  defaultTag: 'p',

  // When recording the history stack, this is the delay time(miliseconds) since the last input
  historyStackDelayTime: 400,

  // Add tags to the default tags whitelist of editor.
  // _defaultTagsWhitelist : 'br|p|div|pre|blockquote|h[1-6]|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|table|thead|tbody|tr|th|td|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup'
  addTagsWhitelist: '',

  // blacklist
  tagsBlacklist: null,
  pasteTagsBlacklist: null,

  // Whitelist of tags when pasting. 
  // _editorTagsWhitelist  : _defaultTagsWhitelist + addTagsWhitelist
  // ex) 'p|h[1-6]'
  pasteTagsWhitelist: _editorTagsWhitelist,

  // Blacklist of the editor default tags. 
  // e.g. 'h1|h2' 
  tagsBlacklist: null,

  //  Blacklist of tags when pasting. 
  // e.g. 'h1|h2' 
  pasteTagsBlacklist: null,


  // Add attributes whitelist of tags that should be kept undeleted from the editor.
  // -- Fixed whitelist --
  // Native attributes: 'contenteditable|colspan|rowspan|target|href|src|class|type'
  // Editor attributes: 'data-format|data-size|data-file-size|data-file-name|data-origin|data-align|data-image-link|data-rotate|data-proportion|data-percentage|origin-size'
  // ex) {
  //  'all': 'style', // Apply to all tags
  //  'input': 'checked' // Apply to input tag
  // }              
  attributesWhitelist: null,

  // blacklist
  attributesBlacklist: null,

  // language object
  lang: lang['en'],

  // change the tag of the default text button. 
  textTags: { bold: 'STRONG', underline: 'U', italic: 'EM', strike: 'DEL' },

  // change default formatBlock array.
  formats: ['p', 'div', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

  // show the number of characters in the editor.   
  charCounter: false,

  // null || 'char' || 'byte' || 'byte-html'
  charCounterType: 'char',

  // text to be displayed in the "charCounter" area of the bottom bar
  charCounterLabel: null,

  // the maximum number of characters allowed to be inserted into the editor.
  maxCharCount: null,

  // the min-width size of the editor.
  minWidth: null,

  // the max-width size of the editor.
  maxWidth: null,

  // the size of the total uploadable images (in bytes).
  imageUploadSizeLimit: null,

  // if true, multiple images can be selected.
  imageMultipleFile: false,

  // allowed extensions like '.jpg, .png ..'
  imageAccept: "*",

  // The url of the image gallery, if you use the image gallery.
  // When "imageUrlInput" is true, an image gallery button is created in the image modal.
  // You can also use it by adding 'imageGallery' to the button list.
  imageGalleryUrl: null,

  // Http Header when get image gallery. 
  imageGalleryHeader: null,

  // 'classic', 'inline', 'balloon', 'balloon-always'
  mode: 'classic',

  // if true, the editor is set to RTL(Right To Left) mode.
  rtl: false,

  // deletes other attributes except for the property set at the time of line break.
  lineAttrReset: '',

  // toolbar width
  toolbarWidth: 'max-content',

  // 'cell', 'top'
  tableCellControllerPosition: 'cell',

  // if true, disables the interaction of the editor and tab key.
  tabDisable: false,

  // You can disable shortcuts.
  // e.g. ['bold', 'strike', 'underline', 'italic', 'undo', 'indent']
  shortcutsDisable: [],
                    
  // If false, hide the shortcuts hint.
  shortcutsHint: true,

  // A custom HTML selector placing the toolbar inside.
  toolbarContainer: null,

  // Sets to -1 or false or null to turn off
  // Sticky Toolbar
  // Default: 0px (offset)
  stickyToolbar: 0,

  // The toolbar is rendered hidden
  hideToolbar: false,

  // top offset value of "full Screen"
  fullScreenOffset: '',

  // the position property of suneditor.   
  position: null,

  // places content in the iframe
  iframe : false,

  // allows the usage of HTML, HEAD, BODY tags and DOCTYPE declaration.
  fullPage: false,

  // Attributes of the iframe
  iframeAttributes: null,

  // CSS file to apply inside the iframe
  iframeCSSFileName: 'suneditor',

  // e.g. &lt;h1&gt;Preview Template&lt;/h1&gt; {{contents}} &lt;div&gt;_Footer_&lt;/div&gt;
  previewTemplate: null,

  // A template of the "print".
  // The {{contents}} part in the HTML string is replaced with the contents of the editor.
  // e.g. "&lt;div style='width:auto; max-width:1080px; margin:auto;'&gt;&lt;h1&gt;Print Template&lt;/h1&gt; {{contents}} &lt;div&gt;_Footer_&lt;/div&gt;&lt;/div&gt;"
  printTemplate: null,

  // CodeMirror option object
  codeMirror: null,

  // katex options
  // https://github.com/KaTeX/KaTeX
  katex: null,

  // Math plugin font size list.
  mathFontSize: [
    {text: '1', value: '1em', default: true},
    {text: '1.5', value: '1.5em'},
    {text: '2', value: '2em'},
    {text: '2.5', value: '2.5em'}
  ],

  // Shows the bottom resizing bar.
  resizingBar: true,

  // Font Family array
  font: ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Georgia','tahoma', 'Trebuchet MS', 'Verdana'],

  // Font Size array
  fontSize: [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72],

  // Font size unit
  fontSizeUnit: 'px',

  // A list of drop-down options for the 'align' plugin
  alignItems: ['right', 'center', 'left', 'justify'],

  // Enables video resizing
  videoResizing: true,

  // width/height of the video
  videoWidth: 560,
  videoHeight: '56.25%',

  // If true, video size can only be scaled by percentage.
  videoSizeOnlyPercentage: false,

  // Choose whether to video rotation buttons display.
  videoRotation: false,

  // The default aspect ratio of the video.
  videoRatio: 0.5625,

  // Video ratio selection options.
  videoRatioList:  [ {name: 'Classic Film 3:2', value: 0.6666}, {name: 'HD', value: 0.5625} ],

  // Choose whether the video height input is visible.
  videoHeightShow: true,

  // Choose whether the video align radio buttons are visible
  videoAlignShow: true,

  // Choose whether the video ratio options is visible.
  videoRatioShow: true,

  // the query string of a YouTube embedded URL. 
  youtubeQuery: '',

  // The query string of a Vimeo embedded URL
  vimeoQuery: '',

  // whether to create a file input tag in the video upload window.
  videoFileInput: false,

  // whether to create a video url input tag in the video upload window.
  // if the value of videoFileInput is false, it will be unconditionally.
  videoUrlInput: true,

  // Http Header when uploading videos.  
  videoUploadHeader: null,

  // the video upload to server mapping address.
  videoUploadUrl: null,

  // the size of the total uploadable videos (in bytes).
  videoUploadSizeLimit:  null,

  // if true, multiple videos can be selected. 
  videoMultipleFile:  false,

  // define "Attributes" of the video tag.
  videoTagAttrs: null,

  // define "Attributes" of the iframe tag
  videoIframeAttrs: null,

  // allowed extensions like '.mp4, .avi ..'
  videoAccept: "*",

  // default width of the audio frame.  
  audioWidth: '300px',

  // default height of the audio frame. 
  audioHeight: default,

  // whether to create a file input tag in the audio upload window
  audioFileInput: false,

  // whether to create a audio url input tag in the audio upload window.
  audioUrlInput: true,

  // Http Header when uploading audios. 
  audioUploadHeader: null,

  // upload url
  audioUploadUrl: null,

  // the size of the total uploadable audios (in bytes).
  // invokes the "onAudioUploadError" method.
  audioUploadSizeLimit: null,

  // if true, multiple audios can be selected.
  audioMultipleFile: false,

  // define "Attributes" of the audio tag.  
  audioTagAttrs: null,

  // allowed extensions like '.mp3, .wav ..'
  audioAccept: "*",

  // default protocol for the links. ('link', 'image', 'video', 'audio')
  linkProtocol: null,

  // default checked value of the "Open in new window" checkbox
  linkTargetNewWindow: false,

  // Allows script tags.
  __allowedScriptTag: false, 

  // the placeholder text.  
  placeholder: null,

  // Activate the media[image, video, audio] selection status immediately after inserting the media tag.
  mediaAutoSelect: true,

  // custom icons
  // {
  //   bold: '<span class="se-icon-text">B</span>',
  //   table: '<i class="&quot;xx"></i>',
  //   insert_row_above: ''
  // }
  icons: null,

  // defines "rel" attribute list of anchor tag
  // e.g. ['author', 'external', 'nofollow']
  linkRel: [],

  // defines default "rel" attributes of anchor tag.
  // e.g.
  // {
  //   default: 'nofollow', // Default rel
  //   check_new_window: 'noreferrer noopener', // When "open new window" is checked 
  //   check_bookmark: 'bookmark' // When "bookmark" is checked 
  // },
  linkRelDefault: {},

  // If true, disables the automatic prefixing of the host URL to the value of the link
  linkNoPrefix: false,

  // Defines the hr items.
  hrItems: [
    {name: lang.toolbar.hr_solid, class: '__se__solid'},
    {name: lang.toolbar.hr_dashed, class: '__se__dashed'},
    {name: lang.toolbar.hr_dotted, class: '__se__dotted'}
  ],

  // Choose whether the image height input is visible.
  imageHeightShow: true,

  // Choose whether the image align radio buttons are visible.
  imageAlignShow: true,

  // enables image resizing
  imageResizing: true,

  // image width/height
  imageWidth: 'auto',
  imageHeight: 'auto',

  // If true, image size can only be scaled by percentage
  imageSizeOnlyPercentage: true,

  // Shows image rotation buttons
  imageRotation: false,

  // image file input
  imageFileInput: true,

  // image url input
  imageUrlInput: true,

  // image upload url
  imageUploadUrl: null,

  // Http Header when uploading images
  imageUploadHeader: null,

  // height/width of the editor
  height: '',
  width: '',

  // min height/width of the editor
  minHeight: null,
  minWidth: null,

  // color array of color picker
  // e.g. [['#ccc', '#dedede', 'OrangeRed', 'Orange', 'RoyalBlue', 'SaddleBrown'], ['SlateGray', 'BurlyWood', 'DeepPink', 'FireBrick', 'Gold', 'SeaGreen']]
  colorList: null,

  // line-height array
  lineHeights: [
    {text: '1', value: 1},
    {text: '1.15', value: 1.15},
    {text: '1.5', value: 1.5},
    {text: '2', value: 2}
  ],

  // Displays the current node structure to resizingBar
  showPathLabel: true,

  // Enable/disable resize function of bottom resizing bar
  resizeEnable: true,

  // A custom HTML selector placing the resizing bar inside
  resizingBarContainer: null,

  // Size of background area when activating dialog window
  popupDisplay: '',

  // CSS display property
  display: 'block',

  // show/hide toolbar icons
  buttonList: [
    ['undo', 'redo'],
    ['font', 'fontSize', 'formatBlock'],
    ['paragraphStyle', 'blockquote'],
    ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
    ['fontColor', 'hiliteColor', 'textStyle'],
    ['removeFormat'],
    '/', // Line break
    ['outdent', 'indent'],
    ['align', 'horizontalRule', 'list', 'lineHeight'],
    ['table', 'link', 'image', 'video', 'audio' /** ,'math' */], // You must add the 'katex' library at options to use the 'math' plugin.
    /** ['imageGallery'] */ // You must add the "imageGalleryUrl".
    ['fullScreen', 'showBlocks', 'codeView'],
    ['preview', 'print'],
    ['save', 'template']
  ]

  // execute a function when the save button is clicked.
  callBackSave: function(){}
  
})</pre>
<p>API methods:</p>
<pre class="brush:javascript">var suneditor = SUNEDITOR.create('Editor');

// Copies the contents of the suneditor into a [textarea]
suneditor.save();

// Updates options
suneditor.setOptions(OPTIONS);

// Gets the suneditor's context object. Contains settings, plugins, and cached element objects
suneditor.getContext();

// Gets the contents of the suneditor
suneditor.getContents();

// Changes the contents of the suneditor
editor.setContents('set contents');

// Inserts an HTML element or HTML string or plain string at the current cursor position
suneditor.insertHTML('&lt;img src="https://suneditor.com/sample/img/sunset.jpg"&gt;');

// Change the contents of the suneditor
suneditor.setContents('set contents');

// Switch to or off "ReadOnly" mode
suneditor.appendContents('append contents');

// readOnly(value)
suneditor.readOnly(value);

// Upload images using image plugin
editor.insertImage(FileList);

// Disable the suneditor
suneditor.disabled();

// Enabled the suneditor
suneditor.enabled();

// Hide the suneditor
suneditor.hide();

// Show the suneditor
suneditor.show();
    
// Destroy the suneditor
suneditor.destroy();

// Open a notice area
suneditor.noticeOpen('test notice');

// Close a notice area
suneditor.noticeClose();

// Gets a list of images uploaded to the editor
/** 
 * {
 *  src: imgage src
 *  index: data index
 *  name: file name
 *  size: file size
 *  select: select function
 *  delete: delete function
 * }
 **/
suneditor.getImagesInfo();</pre>
<p>Event functions.</p>
<pre class="brush:javascript">editor.onload = function (core, reload) {
    console.log('onload-core', core)
    console.log('onload-reload', reload)    
}

suneditor.onScroll = function (e) { console.log('onScroll', e) }

editor.onMouseDown = function (e, core) { console.log('onMouseDown', e) }

suneditor.onClick = function (e) { console.log('onClick', e) }

editor.onInput = function (e, core) { console.log('onInput', e) }

suneditor.onKeyDown = function (e) { console.log('onKeyDown', e) }

suneditor.onKeyUp = function (e) { console.log('onKeyUp', e) }

suneditor.onDrop = function (e) { console.log('onDrop', e) }

editor.onSave = function (contents, core) {console.log(contents) }

suneditor.onChange = function (contents) { console.log('onChange', contents) }

editor.onFocus = function (e, core) { console.log('onFocus', e) }

editor.onBlur = function (e, core) { console.log('onBlur', e) }

editor.onPaste = function (e, cleanData, maxCharCount) { console.log('onPaste', e, cleanData, maxCharCount) }

editor.onPasteMath = function (e, core) { console.log('onPasteMath', e) }

editor.onCopy = function (e, clipboardData, core) { console.log('onCopy', e) }

editor.onCut = function (e, clipboardData, core) { console.log('onCut', e) }

// Called before the image is uploaded
// If false is returned, no image upload is performed.
/**
 * files: Files array
 * info: Input information
 * core: Core object
 * return {Boolean}
 */
editor.onImageUploadBefore: function (files, info, core) {
    console.log('files', files);
    console.log('info', info);
    return Boolean
}

// Called before the video is uploaded
// If true is returned, the internal upload process runs normally.
// If false is returned, no video(iframe, video) upload is performed.
// If new fileList are returned,  replaced the previous fileList
// If undefined is returned, it waits until "uploadHandler" is executed.
/** 
 * files: Files array
 * info: {
 * - inputWidth: Value of width input
 * - inputHeight: Value of height input
 * - align: Align Check Value
 * - isUpdate: Update video if true, create video if false
 * - element: If isUpdate is true, the currently selected video.
 * }
 * core: Core object,
 * uploadHandler: If undefined is returned, it waits until "uploadHandler" is executed.
 *                "uploadHandler" is an upload function with "core" and "info" bound. (plugin.upload.bind(core, info))
 *                [upload files] : uploadHandler(files or [new File(...),])
 *                [error]        : uploadHandler("Error message")
 *                [Just finish]  : uploadHandler()
 *                [directly register] : uploadHandler(response) // Same format as "videoUploadUrl" response
 *                                   ex) {
 *                                      // "errorMessage": "insert error message",
 *                                      "result": [ { "url": "...", "name": "...", "size": "999" }, ]
 *                                   }
 * return {Boolean|Array|undefined}
 */

// Called before the audio is uploaded
// If true is returned, the internal upload process runs normally.
// If false is returned, no audio upload is performed.
// If new fileList are returned,  replaced the previous fileList
// If undefined is returned, it waits until "uploadHandler" is executed.
/** 
 * files: Files array
 * info: {
 * - isUpdate: Update audio if true, create audio if false
 * - currentaudio: If isUpdate is true, the currently selected audio.
 * }
 * core: Core object,
 * uploadHandler: If undefined is returned, it waits until "uploadHandler" is executed.
 *                "uploadHandler" is an upload function with "core" and "info" bound. (plugin.upload.bind(core, info))
 *                [upload files] : uploadHandler(files or [new File(...),])
 *                [error]        : uploadHandler("Error message")
 *                [Just finish]  : uploadHandler()
 *                [directly register] : uploadHandler(response) // Same format as "audioUploadUrl" response
 *                                   ex) {
 *                                      // "errorMessage": "insert error message",
 *                                      "result": [ { "url": "...", "name": "...", "size": "999" }, ]
 *                                   }
 * return {Boolean|Array|undefined}
 */

// Called when the audio is is uploaded, updated, deleted
// -- arguments is same "onImageUpload" --
editor.onAudioUpload = function (targetElement, index, state, info, remainingFilesCount, core) {
  console.log(`targetElement:${targetElement}, index:${index}, state('create', 'update', 'delete'):${state}`)
  console.log(`info:${info}, remainingFilesCount:${remainingFilesCount}`)
}

// Called when the video(iframe, video) upload failed
// -- arguments is same "onImageUploadError" --
editor.onVideoUploadError = function (errorMessage, result, core) {
  alert(errorMessage)
  return Boolean
}
// Called when the audio upload failed
// -- arguments is same "onImageUploadError" --
editor.onAudioUploadError = function (errorMessage, result, core) {
  alert(errorMessage)
  return Boolean
}

// Called when the editor is resized using the bottom bar
// height, prevHeight are number
editor.onResizeEditor = function (height, prevHeight, core) {
  console.log(`height: ${height}, prevHeight: ${prevHeight}`)
}

// Called after the "setToolbarButtons" invocation
// Can be used to tweak buttons properties (useful for custom buttons)
/**
 * buttonList: buttonList array 
 * core: Core object
 */
editor.onSetToolbarButtons = function (buttonList, core) {
  console.log(`buttonList: ${buttonList}`)
}

//**
* @description It replaces the default callback function of the video upload
* xmlHttp: xmlHttpRequest object
* info: Input information
* - inputWidth: Value of width input
* - inputHeight: Value of height input
* - align: Align Check Value
* - isUpdate: Update video if true, create video if false
* - element: If isUpdate is true, the currently selected video.
* core: Core object
*/
editor.videoUploadHandler = function (xmlHttp, info, core) {
  // Editor code
  const response = JSON.parse(xmlHttp.responseText);
  if (response.errorMessage) {
      this.plugins.video.error.call(this, response.errorMessage, response);
  } else {
      this.plugins.video.register.call(this, info, response);
  }
}
/**
* @description It replaces the default callback function of the audio upload
* xmlHttp xmlHttpRequest object
* info Input information
* - isUpdate: Update audio if true, create audio if false
* - element: If isUpdate is true, the currently selected audio.
* core Core object
*/
editor.audioUploadHandler = function (xmlHttp, info, core) {
  // Editor code
  const response = JSON.parse(xmlHttp.responseText);
  if (response.errorMessage) {
      this.plugins.audio.error.call(this, response.errorMessage, response);
  } else {
      this.plugins.audio.register.call(this, info, response);
  }
}

// Called just after the controller is positioned and displayed on the screen.
// controller - editing elements displayed on the screen [image resizing, table editor, link editor..]]
/**
* name: The name of the plugin that called the controller
* controllers: Array of Controller elements
* core: Core object
*/
editor.showController = function (name, controllers, core) {
  console.log('plugin name', name);
  console.log('controller elements', controllers);
}

// It replaces the default callback function of the image upload
/**
 * response: Response object
 * info (Input information): {
 * - linkValue: Link url value
 * - linkNewWindow: Open in new window Check Value
 * - inputWidth: Value of width input
 * - inputHeight: Value of height input
 * - align: Align Check Value
 * - isUpdate: Update image if true, create image if false
 * - currentImage: If isUpdate is true, the currently selected image.
 * }
 * core: Core object
 */
editor.imageUploadHandler = function (response, info, core) {
    // Example of upload method
    const res = JSON.parse(response.responseText);
    
    // Error
    if (res.errorMessage) {
        if (typeof editor.onImageUploadError === 'function') {
            if (core.onImageUploadError(res.errorMessage, res.result)) {
                core.notice.open.call(core, res.errorMessage);
            }
        } else {
            core.notice.open.call(core, res.errorMessage);
        }
        /** 
         * You can do the same thing using the core private function.
         * The core._imageUploadError function returns false when "editor.onImageUploadError" function is not defined.
        */
        // if (core._imageUploadError(res.errorMessage, res.result)) {
        //     core.notice.open.call(core, res.errorMessage);
        // }
    }
    // Success
    else {
        const fileList = res.result;
        const imagePlugin = core.plugins.image;

        for (let i = 0, len = fileList.length, file; i &lt; len; i++) {
            // The file object must have name and size attributes.
            file = {name: fileList[i].name, size: fileList[i].size};
            // For existing image updates, the "info" attributes are predefined in the element.
            // The "imagePlugin.update_src" function is only changes the "src" attribute of an image.
            if (info.isUpdate) imagePlugin.update_src.call(core, fileList[i].url, info.currentImage, file);
            // The image is created and a format element(p, div..) is added below it.
            else imagePlugin.create_image.call(core, fileList[i].url, info.linkValue, info.linkNewWindow, info.inputWidth, info.inputHeight, info.align, file);
        }
    }
}

// An event when toggling between code view and wysiwyg view.
/**
 * isCodeView: Whether the current code view mode
 * core: Core object
 */
editor.toggleCodeView = function (isCodeView, core) {
  console.log('isCodeView', isCodeView);
}

// An event when toggling full screen.
/**
 * isFullScreen: Whether the current full screen mode
 * core: Core object
 */
editor.toggleFullScreen = function (isFullScreen, core) {
  console.log('isFullScreen', isFullScreen);
}

// Called when the video(iframe) is is uploaded, updated, deleted
/**
* targetElement: Current iframe element
* index: Uploaded index
* state: Upload status ('create', 'update', 'delete')
* videoInfo: {
* - index: data index
* - select: select function
* - delete: delete function
* - element: iframe element
* - src: src attribute of iframe tag
* }
* remainingFilesCount: Count of remaining files to upload (0 when added as a url)
* core: Core object
*/
editor.onVideoUpload = function (targetElement, index, state, videoInfo, remainingFilesCount, core) {
  console.log(`targetElement:${targetElement}, index:${index}, state('create', 'update', 'delete'):${state}`)
  console.log(`videoInfo:${videoInfo}, remainingFilesCount:${remainingFilesCount}`)
}

// Called before the editor's default event action
editor.showInline = function (toolbar, context) {
  console.log('toolbar', toolbar);
  console.log('context', context);
}

// Called when the image is uploaded or the uploaded image is deleted.
suneditor.onImageUpload = function (targetImgElement, index, isDelete, imageInfo) {
  console.log('targetImgElement :' + targetImgElement + ', index : ' + index + ', isDelete : ' + isDelete)
  console.log(imageInfo)
}

// Called when the image is upload failed.
// If you return false, the default notices are not called.
suneditor.onImageUploadError = function (errorMessage, result) {
  alert(errorMessage)
}</pre>
<h2>Changelog:</h2>
<p>v3.1.2 (04/20/2026)</p>
<ul>
<li>Updated design border-radius values</li>
<li>Bugfixes</li>
</ul>
<p>v3.1.1 (04/14/2026)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v3.1.0 (04/12/2026)</p>
<ul>
<li>Improve MS Office HTML paste conversion (src/helper/msOffice.js).</li>
<li>Bugfixes.</li>
</ul>
<p>v3.0.6 (04/08/2026)</p>
<ul>
<li>Default delayTime for the mention plugin changed from 200 to 120</li>
<li>The toolbar selectAll button now selects the entire editor content immediately instead of stepping through scopes (keyboard Ctrl+A still uses scope stepping)</li>
<li>Improved browser search to query all folders instead of only the currently selected folder</li>
<li>Added keyword highlighting to sbrowser search results for matching file names</li>
<li>Added a clear button to the browser search form to reset the search</li>
<li>An expand option has been added to the file browser to control initial folder expand depth (default: 1)</li>
<li>Fixed a bug where switching folders in the file browser did not update the item list and tags, causing search and tag filters to operate on stale data</li>
</ul>
<p>v2.47.7 (08/23/2025)</p>
<ul>
<li>Fixed an issue where images in the image gallery were not clickable when their size was smaller than the label.</li>
<li>Fixed an issue where window.SUNEDITOR did not include the init function.</li>
<li>Fixed a bug where media components were not properly restructured in certain tag structures.</li>
</ul>
<p>v2.47.6 (07/04/2025)</p>
<ul>
<li>Improved to prevent certain structures from being broken when pasting HTML formats.</li>
<li>Fixed a bug related to removing selected content.</li>
<li>Fixed a bug where the &#8220;pt&#8221; font size conversion value was incorrect.</li>
<li>Fixed a bug where special spaces disappeared when viewing HTML code.</li>
<li>Fixed a bug where the attributeWhitelist option was not applied properly.</li>
</ul>
<p>v2.47.5 (02/06/2025)</p>
<ul>
<li>Fixed a bug where a &lt;font&gt; tag was unexpectedly rendered in certain situations when removing text style nodes.</li>
</ul>
<p>v2.47.4 (02/04/2025)</p>
<ul>
<li>Fixed bug with Enter key behavior in certain situations.</li>
<li>Added Hungarian(hu) translation</li>
</ul>
<p>v2.47.3 (01/23/2025)</p>
<ul>
<li>Fixed TypeScript error in &#8220;imageGalleryData&#8221; option.</li>
</ul>
<p>v2.47.2 (01/16/2025)</p>
<ul>
<li>Fixed a bug where line breaks would continue to be added in the code view when &#8220;strictMode&#8221; is &#8220;false&#8221;.</li>
<li>Changed &#8216;strictHTMLValidation&#8217; option to &#8216;true&#8217;.</li>
</ul>
<p>v2.47.1 (01/09/2025)</p>
<ul>
<li>A &#8220;strictHTMLValidation&#8221; option has been added.</li>
<li>A &#8220;imageGalleryData&#8221; option has been added.</li>
<li>Improved missing TypeScript parts.</li>
<li>Improved mobile check logic.</li>
<li>Bugfix.</li>
</ul>
<p>v2.47.0 (08/12/2024)</p>
<ul>
<li>Added vimeoQuery option.</li>
<li>Added onPasteMath(event, core) event.</li>
<li>Fixed bugs.</li>
</ul>
<p>v2.46.3 (05/28/2024)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.46.2 (04/25/2024)</p>
<ul>
<li>Fixed a bug where the &#8220;line-height&#8221; style was not maintained for the format line.</li>
</ul>
<p>v2.46.1 (04/23/2024)</p>
<ul>
<li>Added Czech(cs) translation</li>
<li>Fixed bugs</li>
</ul>
<p>v2.46.0 (04/14/2024)</p>
<ul>
<li>Added Farsi(persian) translation</li>
<li>Added Turkish(tr) translation added</li>
<li>Added &#8220;strictMode&#8221; option</li>
<li>Added on[Audio, Image, Video]DeleteBefore event</li>
<li>Improved selection experience</li>
<li>Improved much better fileComponent keyboard experience</li>
<li>Fixed bugs</li>
</ul>
<p>v2.45.0 (06/19/2023)</p>
<ul>
<li>Added allowClassName option.</li>
<li>Added __allowedScriptTag option.</li>
<li>Improved to operate properly within the editor when using &lt;input&gt; tags.</li>
<li>Even when using &lt;div&gt; as a target element, the content inside the tag has been improved so that it is designated as the default value.</li>
<li>Bugfixes</li>
</ul>
<p>v2.44.10 (04/20/2023)</p>
<ul>
<li>Bugfixes</li>
<li>Some elements created in the editor have been improved so that the contenteditable attribute is not included.</li>
</ul>
<p>v2.44.10 (04/11/2023)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.44.8 (04/10/2023)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.44.6 (03/31/2023)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.44.5 (03/28/2023)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.44.4 (03/28/2023)</p>
<ul>
<li>Urdu(ur) translation added.</li>
<li>Portuguese(pt_br) translation fixed.</li>
<li>Add &#8220;contents&#8221; parameter to onBlur event.</li>
<li>Improvement of applying line breaks when pasting.</li>
<li>The getContents() method has been improved to return after cleaning the HTML.</li>
<li>The child nodes of .__se__tag tag have also been improved so that the structure and properties are maintained.</li>
</ul>
<p>v2.44.3 (10/08/2022)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.44.2 (09/29/2022)</p>
<ul>
<li>Fixed have been made so that the same tags do not continue to overlap when pasting styled text.</li>
<li>Fixed a bug where unintentional styles were applied when pasting.</li>
</ul>
<p>v2.44.1 (09/25/2022)</p>
<ul>
<li>Fixed a bug where unintentional styles were applied when pasting.</li>
</ul>
<p>v2.44.0 (09/22/2022)</p>
<ul>
<li>Bugfix</li>
</ul>
<p>v2.43.14 (07/05/2022)</p>
<ul>
<li>Fixed a bug where the edit window did not work properly when a &#8220;Katex&#8221; error occurred in the &#8220;Math&#8221; plugin.</li>
</ul>
<p>v2.43.13 (06/25/2022)</p>
<ul>
<li>When editing a link, it has been improved so that the link appears as entered.</li>
<li>Fixed a bug where editor links were not displayed in the edit window when using linkNoPrefix.</li>
</ul>
<p>v2.43.12 (06/23/2022)</p>
<ul>
<li>Fixed content style bug when loaded</li>
</ul>
<p>v2.43.10/11 (06/18/2022)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.43.9 (06/11/2022)</p>
<ul>
<li>Fixed a bug where the tag properties were deleted of the value of &#8220;options.value&#8221; and the value modified in code view mode.</li>
<li>Added &#8220;hideToolbar&#8221; option.</li>
</ul>
<p>v2.43.8 (05/31/2022)</p>
<ul>
<li>Modified so that excessive &#8220;span&#8221; tags are not generated when copying/pasting text in the Chrome browser.</li>
<li>Bugfix</li>
</ul>
<p>v2.43.6 (05/20/2022)</p>
<ul>
<li>Fixed a bug where the button activation effect did not work properly when using the responsive toolbar.</li>
<li>The behavior when pasting into a list has been improved.</li>
<li>Added &#8220;aria-label&#8221; attribute to button.</li>
</ul>
<p>v2.43.5 (05/15/2022)</p>
<ul>
<li>Fixed bugs</li>
</ul>
<p>v2.43.4 (05/06/2022)</p>
<ul>
<li>Fixed a bug where the balloon toolbar was not displayed when selecting all</li>
</ul>
<p>v2.43.3 (05/06/2022)</p>
<ul>
<li>Fixed a bug where the video was not resizing properly.</li>
</ul>
<p>v2.43.2 (05/03/2022)</p>
<ul>
<li>Fixed a bug where placeholders did not appear.</li>
<li>Fixed a IE syntax error.</li>
</ul>
<p>v2.43.1 (04/26/2022)</p>
<ul>
<li>Fixed a bug where placeholders did not appear.</li>
</ul>
<p>v2.43.0 (04/23/2022)</p>
<ul>
<li>Update</li>
</ul>
<p>v2.42.0 (01/14/2022)</p>
<ul>
<li>French (fr) translation fixed.</li>
<li>onSetToolbarButtons event callback has been added.</li>
<li>onSave event callback has been added.</li>
<li>iframeAttributes option has been added.</li>
<li>[image, video]AlignShow options has been added.</li>
<li>attributesBlacklist, tagsBlacklist, pasteTagsBlacklist options has been added.</li>
<li>dir, dir_ltr, dir_rtl buttons has been added.</li>
<li>&#8220;#fix&#8221; feature has been added to the button group.</li>
<li>linkTargetNewWindow option has been added.</li>
<li>nullMessage, thumbnail attributes has been added to the &#8220;imageGalleryUrl&#8221; response data.</li>
<li>resizeEnable, resizingBarContainer options has been added.</li>
<li>alignItems option has been added.</li>
<li>lineAttrReset option has been added.</li>
<li>hrItems option has been added.</li>
<li>Fixed to allow empty anchor tags in editor.</li>
<li>Added alt attribute to &#8220;imageInfo&#8221;.</li>
<li>Fixed to automatically remove trailing whitespace from selected text when creating a link.</li>
<li>Bugfix</li>
</ul>
<p>v2.41.3 (07/03/2021)</p>
<ul>
<li>Fixed a bug where some formats were not properly entered when pasting from the Word.</li>
</ul>
<p>v2.41.2 (06/28/2021)</p>
<ul>
<li>Exception handling to the readOnly mode has been added.</li>
</ul>
<p>v2.41.1 (06/26/2021)</p>
<ul>
<li>Fixed bugs in readOnly mode.</li>
<li>XSS attack vulnerability fixes.</li>
<li>Fixed a bug where the hr tag was not deleted.</li>
<li>Fixed a bug when media tag init.</li>
</ul>
<p>v2.41.0 (06/22/2021)</p>
<ul>
<li>Dutch (nl) translation fixed.</li>
<li>A readOnly method has been added.</li>
<li>It has been modified not to check the default format in the __se__tag class.</li>
<li>Added details, summary tags to the default tags.</li>
<li>In &#8220;Code View&#8221; mode, it has been modified not to check the format duplicate.</li>
</ul>
<p>v2.40.0 (06/08/2021)</p>
<ul>
<li>Bugfix</li>
</ul>
<p>v2.39.0 (05/16/2021)</p>
<ul>
<li>The onResizeEditor event has been added.</li>
<li>Improved tag indent and line break in code view mode.</li>
<li>Fixed a bug that TypeScript build fails.</li>
<li>Fixed a bug when paste from &#8220;Notepad&#8221; displayed &lt;br&gt; tag.</li>
<li>Fixed a bug that HTML converter.</li>
<li>XSS attack vulnerability fixes.</li>
</ul>
<p>v2.38.10 (05/11/2021)</p>
<ul>
<li>&#8220;Font&#8221; and &#8220;Size&#8221; have been modified to display default values.</li>
<li>Fixed a bug where the text style was not maintained when entering the enter key at the end of the text.</li>
</ul>
<p>v2.38.8 (05/07/2021)</p>
<ul>
<li>Fixed a bug where line breaks disappear when pasting.</li>
</ul>
<p>v2.38.7 (05/06/2021)</p>
<ul>
<li>When pasting an external <strong>image</strong> or <strong>video</strong>, a bug that sometimes the <strong>element</strong> was not properly added has been fixed.</li>
<li>Fixed a bug in which the default action was not canceled even when returning false in the <code>user event</code>.</li>
</ul>
<p>v2.38.6 (05/04/2021)</p>
<ul>
<li>The user event location has been modified so that the current event action can be canceled by a user event.</li>
</ul>
<p>v2.38.5 (04/28/2021)</p>
<ul>
<li>Fixed a bug where the other text style was not removed when the font-size style was applied.</li>
</ul>
<p>v2.38.4 (04/28/2021)</p>
<ul>
<li>Fixed a bug that core.insertNode() is creates a new line when inserted in the middle of the string has been confirmed.</li>
<li>Fixed a bug that TypeScript build fails.</li>
<li>Fixed a bug where the default color of the font was not applied properly.</li>
</ul>
<p>v2.38.4 (04/17/2021)</p>
<ul>
<li>The addTagsWhitelist option has been updated to support tags with a dash(-) in the name.</li>
</ul>
<p>v2.38.3 (04/16/2021)</p>
<ul>
<li>When pasting an external image or video, a bug that sometimes the element was not properly added has been fixed</li>
<li>XSS attack vulnerability fixes.</li>
</ul>
<p>v2.38.1 (04/11/2021)</p>
<ul>
<li>The &#8220;z-index&#8221; and &#8220;position&#8221; properties of options.defaultStyle have been modified to be applied to the top div.</li>
<li>Fiexed a bug where KaTeX rendering was broken, since 2.37.4 version.</li>
</ul>
<p>v2.38.0 (04/11/2021)</p>
<ul>
<li>Added printTemplate option</li>
<li>Adapt module export for Typescript 2.6</li>
<li>Updated Typescript definitions</li>
<li>Modified the anchor module to can use the &#8220;tel:&#8221; and &#8220;sms:&#8221;</li>
<li>Fiexed &#8220;Request-Headers&#8221; error at the ImageGallery</li>
<li>Added media component exception handling</li>
<li>Fixed a bug where tag would render when pasting text containing HTML tags</li>
<li>Fixed a bug that focusing on the editor when using the core.setContents() method</li>
</ul>
<p>v2.37.4 (04/01/2021)</p>
<ul>
<li>XSS attack vulnerability fixes</li>
</ul>
<p>v2.37.3 (03/30/2021)</p>
<ul>
<li>Fixed a bug in which the attribute values of the tag were converted not conforming to the HTML syntax.</li>
<li>Fixed a bug that focused on the editor when using the core.setContents() method.</li>
</ul>
<p>v2.37.2 (03/29/2021)</p>
<ul>
<li>Fixed a bug losing line break and attr when pasted.</li>
<li>Modified list alignment default style to &#8220;outside&#8221;.</li>
</ul>
<p>v2.37.1 (03/19/2021)</p>
<ul>
<li>Fixed IE syntax error.</li>
<li>Fixed a bug where the super/subscript toggle did not work after updated v.2.35.0.</li>
</ul>
<p>v2.37.0 (03/17/2021)</p>
<ul>
<li>It has been improved to be able to select an internal header tag when entering a bookmark in the anchor module.</li>
<li>Added more options</li>
<li>Fixed bugs</li>
</ul>
<p>v2.36.5 (03/05/2021)</p>
<ul>
<li>Added _printClass option to define the class of print and preview.</li>
<li>Fixed a bug in which changing the lang option did not work with the setOptions() method.</li>
<li>Fixed a bug in which activation/deactivation of the indent icon did not work properly.</li>
<li>Fixed a bug when adding components such as images and videos from the horizontal line, a bug that was added at the wrong location.</li>
<li>Katex has been modified to always operate in LTR mode.</li>
</ul>
<p>v2.36.4 (02/21/2021)</p>
<ul>
<li>Fixed a bug where the link of the image disappeared when returning to the WYSIWYG mode from the code view mode</li>
<li>Fixed a bug in which the size of the rotated image or video was not properly adjusted</li>
<li>When enviroment &#8220;shadowRoot&#8221;, the location error of the toolbar and menu has been fixed</li>
</ul>
<p>v2.36.3 (02/19/2021)</p>
<ul>
<li>Fixed a bug in which the size of the rotated image or video was not properly adjusted.</li>
</ul>
<p>v2.36.2 (02/18/2021)</p>
<ul>
<li>Bugfix</li>
<li>Hebrew (he) translation added</li>
<li>Italian(it) translation fixed</li>
</ul>
<p>v2.36.1 (02/01/2021)</p>
<ul>
<li>Fixed a bug in which buttons that were disabled when selecting a component were reactivated.</li>
<li>Fixed a bug where the current color was not displayed on the input of the color selector.</li>
</ul>
<p>v2.36.0 (01/29/2021)</p>
<ul>
<li>Modified to can use the use of &#8220;mailto:&#8221; at the link module.</li>
<li>linkRelDefault option has been added.</li>
<li>mediaAutoSelect option has been added.</li>
<li>A &#8220;download link&#8221; check box was added to the link module.</li>
<li>The link module`s &#8220;rel&#8221; option has been modified to enable duplicate selection.</li>
<li>The protocol display of the link module has been improved.</li>
<li>Bugfix</li>
</ul>
<p>v2.35.1 (01/22/2021)</p>
<ul>
<li>Fixed a bug where table cell split did not work.</li>
<li>Fixed a bug in which content was pasted as an image when pasting from MS Word and Excel.</li>
</ul>
<p>v2.35.0 (01/19/2021)</p>
<ul>
<li>Swedish (se) translation added.</li>
<li>Ukrainian (ua) translation added.</li>
<li>You can enter &#8220;nonbreaking space character&#8221;. Windows: Ctrl + Shift + Space. Mac: Option(⌥) + Shift + Space</li>
<li>The textTags option has been added.</li>
<li>A bookmark button has been added to the link plugin.</li>
<li>The linkRel option has been added.</li>
<li>The fullScreenOffset option has been added.</li>
<li>Added setIframeContents, blur method to the core.</li>
<li>Empty array values are allowed in the buttonList.</li>
<li>Save button shortcut has been added.</li>
<li>&#8220;dir&#8221; argument has been added to the core.isEdgePoint method.</li>
<li>Removed the core.eventStop method.</li>
<li>Added options property to the core.</li>
<li>Bugfix</li>
</ul>
<p>v2.34.3 (11/12/2020)</p>
<ul>
<li>When switching from the &#8220;code view mode&#8221; or when using core.setContents or core.appendContens(), a bug in which HTML classes etc. disappeared has been fixed.</li>
<li>A bug in which the tag related whitelist option was not properly applied has been fixed.</li>
</ul>
<p>v2.34.3 (11/12/2020)</p>
<ul>
<li>Added defaultTag option</li>
<li>When Nullish value is given as an argument of core.setContents(), the default tag is also removed</li>
<li>Fixed an XSS attack vulnerability</li>
<li>Bugs fixed</li>
</ul>
<p>v2.34.2 (11/11/2020)</p>
<ul>
<li>fix: whitelist</li>
</ul>
<p>v2.34.1 (10/31/2020)</p>
<ul>
<li>Modified so that the toolbarContainer option can be used in balloon mode</li>
<li>Shortcuts text of tooltip have been modified to make them more visible</li>
<li>Modified so that the same buttonList object can be used in two or more editors</li>
<li>Bugfix</li>
</ul>
<p>v2.34.0 (10/18/2020)</p>
<ul>
<li>Added rtl(Right to Left) option</li>
<li>Added previewTemplate option was added</li>
<li>Modified to allow other 200 range success responses when uploading files</li>
<li>Paste and drop event parameters have been modified</li>
<li>Bugfix</li>
</ul>
<p>v2.33.3 (09/18/2020)</p>
<ul>
<li>Hotfix</li>
</ul>
<p>v2.33.2 (09/16/2020)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.33.1 (09/06/2020)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.33.0 (09/04/2020)</p>
<ul>
<li>Added more translations and options.</li>
<li>Enhancement and bugfix.</li>
</ul>
<p>v2.32.1 (07/23/2020)</p>
<ul>
<li>A bug that the onload event was not worked has been fixed.</li>
</ul>
<p>v2.32.1 (07/23/2020)</p>
<ul>
<li>A bug that the onload event was not worked has been fixed.</li>
</ul>
<p>v2.31.2 (07/20/2020)</p>
<ul>
<li>Added audioTagAttrs, videoTagAttrs, and videoIframeAttrs options</li>
<li>Added onCopy and onCut events</li>
<li>Added Copy(ctrl+c) and cut(ctrl+x) event of components such as images, video, and audio</li>
</ul>
<p>v2.31.2 (07/15/2020)</p>
<ul>
<li>Added Vimeo as a target for video URLs.</li>
<li>Updated to be able to register immediately using &#8220;uploadHandler&#8221; after upload is completed in &#8220;on[Image,Video,Audio]BeforeUpload&#8221;.</li>
<li>Fixed a bug where options.icons were not applied when using the responsive toolbar.</li>
</ul>
<p>v2.31.1 (07/12/2020)</p>
<ul>
<li>Fixed (re)Init Error</li>
</ul>
<p>v2.31.0 (07/05/2020)</p>
<ul>
<li>Added &#8216;value&#8217; option</li>
<li>Fixed a bug where multiple images were not inserted when using &#8220;base64&#8221; encoding</li>
<li>Fixed a bug that lose focus on the editor after deleting the component when there were only components in the editor</li>
</ul>
<p>v2.30.7 (07/01/2020)</p>
<ul>
<li>fix: set defaultStyle</li>
</ul>
<p>v2.30.6 (06/26/2020)</p>
<ul>
<li>In the &#8220;on[Image,Video,Audio]uploadBefore&#8221; function, instead of executing the default &#8220;uploadHandler&#8221;, the image can be registered by calling the &#8220;register&#8221; method that receives the &#8220;response&#8221; object as an argument value.</li>
<li>Fixed a bug where the focus was on the image caption when selecting an image in Chrome.</li>
</ul>
<p>v2.30.5 (06/25/2020)</p>
<ul>
<li>Added closureRangeFormat format</li>
<li>Bugs fixed</li>
</ul>
<p>v2.30.4 (06/21/2020)</p>
<ul>
<li>Fixed a bug that image inserted was not worked when using imageGallery in the image modal.</li>
<li>Modified the button&#8217;s active class to disappear after the blur event.</li>
</ul>
<p>v2.30.1 (06/19/2020)</p>
<ul>
<li>Added Polish (pl) translation</li>
<li>Added linkProtocol option</li>
<li>Added &#8220;uploadHandler&#8221; argument to the [image,video,audio]UploadBefore event.</li>
<li>Added [image,video,audio]UploadMultiple option</li>
<li>Added closureFreeFormat format and options</li>
<li>Updated to allow drag and drop in the editor (Not working in IE)</li>
<li>Lots of bugs fixed</li>
</ul>
<p>v2.30.0 (06/10/2020)</p>
<ul>
<li>Added Romanian (ro) translation</li>
<li>Added image gallery</li>
<li>Updated buttonList feature</li>
<li>Added more options</li>
<li>Added toggleCodeView and toggleFullScreen events</li>
<li>Inhancenment and bugfixes</li>
</ul>
<p>v2.29.0 (05/07/2020)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.28.4 (04/08/2020)</p>
<ul>
<li>A fixed column width button has been added to the table editing controller.</li>
<li>A onVideoUpload event and getVideosInfo function has been added.</li>
<li>Bugfixes.</li>
</ul>
<p>v2.28.3 (04/05/2020)</p>
<ul>
<li>d.ts files have been added so that the editor can support TypeScript.</li>
<li>Modified the z-index of the controller to be higher than the toolbar.</li>
<li>Bugs fixed</li>
</ul>
<p>v2.28.2 (04/04/2020)</p>
<ul>
<li>Fixed a bug with text nodes disappearing when changing text style in nested lists.</li>
<li>Improved the display location of the balloon-toolbar and submenu when there is not enough space on the page.</li>
<li>Fixed a bug that sometimes the focus did not move to the end depending on the type of the inserted node in the functions.insertHTML method.</li>
</ul>
<p>v2.28.1 (04/02/2020)</p>
<ul>
<li>Added line breaker to add lines when there are no lines above or below the component.</li>
<li>The underline has been replaced from &lt;ins&gt; to &lt;u&gt;.</li>
<li>Added options imageHeight and videoHeight.</li>
<li>Added type the &#8220;byte-html&#8221; to the charCounterType option.</li>
<li>Improve the consistency check before rendering the HTML in the editor.</li>
<li>The method of adding of the custom plugin has been improved.</li>
<li>Bugfix</li>
</ul>
<p>v2.28.0 (03/22/2020)</p>
<ul>
<li>Added icons, charCounterType, and charCounterLabel options</li>
<li>Added onInput event</li>
<li>Bugs fixed</li>
</ul>
<p>v2.27.1 (03/21/2020)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.27.0 (03/20/2020)</p>
<ul>
<li>Bugs fixed</li>
</ul>
<p>v2.26.0 (03/11/2020)</p>
<ul>
<li>Added balloon-always mode.</li>
<li>Added a math plugin that uses the &#8220;KaTeX&#8221; library.</li>
<li>The katex option added.</li>
<li>Added nested lists feature.</li>
<li>Added a blockquote command plugin.</li>
<li>The method of adding a custom formatBlock and the class name have been modified. (&#8220;freeFormat&#8221; added)</li>
<li>The pre(code) tag format has been modified to match that of other editor. (Like <a href="https://www.jqueryscript.net/text/jQuery-WYSIWYG-Rich-Text-Editor-Plugin-Froala-Editor.html" target="_blank" rel="noopener noreferrer">Froala editor</a>)</li>
<li>The formatBlock has been modified so that the name of the format menu is displayed instead of the tag.</li>
<li>The toolbar no longer appears when the focus is on a table when in balloon mode.</li>
<li>Added onBlur and onFocus events.</li>
<li>Added addTagsWhitelist, pasteTagsWhitelist and attributesWhitelist options to possible edit the whitelist in the editor.</li>
<li>Added onImageUploadBefore and imageUploadHandler functions.</li>
<li>The &#8220;core&#8221; object has been added to the arguments of events and functions.</li>
<li>Added functions and hasFocus to the &#8220;core&#8221; object.</li>
</ul>
<p>v2.25.0 (01/16/2020)</p>
<ul>
<li>German(de) translation has been fixed.</li>
<li>Added the onload event.</li>
<li>Fixed a bug that position of placeholder to be incorrect when opening a notice.</li>
<li>Fixed a bug that focus to disappear after text align.</li>
<li>Fixed bug where text was not aligned in IE browser.</li>
</ul>
<p>v2.24.0 (01/15/2020)</p>
<ul>
<li>German(de) translation has been fixed.</li>
<li>Modified anchor tag to remain un split when changing text nodes of an anchor tag.</li>
<li>Toolbar-related methods have been added.</li>
<li>Bugs fixed</li>
</ul>
<p>v2.23.4 (12/22/2019)</p>
<ul>
<li>Fixed a bug where the history stack did not change when characters were deleted in version 2.23.3</li>
</ul>
<p>v2.23.3 (12/20/2019)</p>
<ul>
<li>Changed the &#8220;selection&#8221; object to be deleted when selecting an image or video component.</li>
<li>Modified to give focus to the first TD when creating a table.</li>
<li>Added an &#8220;element&#8221; attribute that is an element of an image tag to &#8220;imagesInfo&#8221;.</li>
<li>Added the &#8220;notHistoryPush&#8221; argument to the core.insertComponent method.</li>
<li>Added removeRange, selectComponent, focusEdge method to core object.</li>
<li>Bugs fixed.</li>
</ul>
<p>v2.23.1 (12/13/2019)</p>
<ul>
<li>Added &#8220;ignoreChangeEvent&#8221; argument to history.reset method.</li>
<li>Added Spanish(es) translation.</li>
</ul>
<p>v2.23.0 (12/12/2019)</p>
<ul>
<li>Enhancement &amp; bugfixed</li>
</ul>
<p>v2.22.0 (12/03/2019)</p>
<ul>
<li>Improved to minimize duplicate tags when tags are added, changed or deleted.</li>
<li>Added &#8220;textStyle&#8221; and &#8220;paragraphStyle&#8221; plugins.</li>
<li>The unlink button has been added to the &#8220;link&#8221; plugin.</li>
<li>Added &#8220;imageSizeUnit&#8221; and &#8220;imageRotation&#8221; options.</li>
<li>Improved line breaks and indents in Code view mode.</li>
<li>Modified to cancel with ESC key while resizing images and videos.</li>
<li>Fixed the &#8220;iframeCSSFileName&#8221; option to use arrays.</li>
<li>The custom format attribute names for the &#8220;formats&#8221; option have been changed.</li>
<li>The &#8220;PRE&#8221; format tag has been fixed to do not create scroll.</li>
<li>The copyTagAttributes and isSameAttributes methods have been added to the util object.</li>
<li>The core.nodeChange method has been improved.</li>
</ul>
<p>v2.21.2 (11/16/2019)</p>
<ul>
<li>Improved line breaks in tags in Code view mode.</li>
<li>Bugs fixed.</li>
</ul>
<p>v2.21.1 (11/12/2019)</p>
<ul>
<li>Fixed the line format to be initialized to paragraph tag after remove attributes when the backspace key is pressed when there is no content in the editor.</li>
<li>Fixed the style of the line format(H1, P, List.. etc.) to be maintained when the line format is changed.<br />
(text-align, line-height.. etc).</li>
<li>Fixed a bug where the history stack did not work properly when the &#8220;fullPage&#8221; option was true.</li>
<li>The &#8220;onlyContents&#8221; argument has been added to the &#8220;getContents&#8221; method.</li>
</ul>
<p>v2.21.0 (11/11/2019)</p>
<ul>
<li>Fixed French translation</li>
<li>After creating the editor, the user object has been modified to allow access to the core and util objects.<br />
(Now can redefine the method after creating the editor.)</li>
<li>Added &#8220;lineHeight&#8221; plugin and button.</li>
<li>Added &#8220;lineHeights&#8221;, &#8220;fontSizeUnit&#8221; options.</li>
</ul>
<p>v2.20.1 (10/15/2019)</p>
<ul>
<li>Fixed a bug where adding a placeholder option with the setOptions method did not result in a placeholder.</li>
<li>Fixed a bug that did not update the history stack after calling removeNode method.</li>
<li>Fixed a bug in which the position value of the resize module such as an image was calculated incorrectly</li>
</ul>
<p>v2.20.0 (10/11/2019)</p>
<ul>
<li>Added placeholder option.</li>
<li>Bugfixed.</li>
</ul>
<p>v2.19.1 (09/30/2019)</p>
<ul>
<li>Fix usage when the Editor is rendered in a popup window.</li>
<li>Fixed the link generation after selecting the text range.</li>
<li>Fixed a bug where paste (ctrl + v) was empty when cutting or copying text wrapped with the return line (shift + enter) in Firefox.</li>
<li>Added &#8220;iframe&#8221;, &#8220;pullPage&#8221;, &#8220;iframeCSSFileName&#8221; options.</li>
<li>Added &#8220;position&#8221; option.</li>
<li>Added &#8220;codeMirror&#8221; option.</li>
<li>Added &#8220;onPaste&#8221; and &#8220;showInline&#8221; event to functions.</li>
<li>The &#8220;formats&#8221; option has been modified to use custom tag as well.</li>
<li>Switch spelling from hilite -&gt; highlight.</li>
<li>A bug that did not apply negative values to the sticky toolbar option has been fixed.</li>
<li>The class name of suneditor-contents has been modified.</li>
<li>The addDocEvent, removeDocEvent methods, _wd, and _ww objects have been added to core.</li>
<li>The getOffset method in util has been modified.</li>
</ul>
<p>v2.18.0 (08/08/2019)</p>
<ul>
<li>Added setOptions method.</li>
<li>Added Template plugins.</li>
<li>Added sort and caption buttons to the resizing module.</li>
<li>The icons in the editor has been changed.</li>
<li>Fixed the &#8220;on&#8221; method to be called when calling a submenu on &#8220;core&#8221;.</li>
<li>The default width of &#8220;toolbar&#8221; has been changed from &#8220;max-content&#8221; to &#8220;auto&#8221;.</li>
<li>When using the &#8220;appendContents&#8221; and &#8220;setContents&#8221; functions, the content is added to match the editor&#8217;s format.</li>
<li>Added &#8220;setContents&#8221; method to &#8220;core&#8221; object.</li>
<li>Bugs fixed.</li>
</ul>
<p>v2.17.3 (07/23/2019)</p>
<ul>
<li>Added the sort button to the resizing module.</li>
<li>Fixed a bug where the width of the image was intermittently 0 when uploading images from &#8220;insertImage&#8221;.</li>
<li>Added &#8220;access denied&#8221; error exception handling to &#8220;getPageStyle&#8221; method.</li>
</ul>
<p>v2.17.1/2 (07/22/2019)</p>
<ul>
<li>Added The &#8220;insertImage&#8221; method to upload images outside the editor.</li>
<li>Added &#8220;imageUploadSizeLimit&#8221; option to limit image upload size.</li>
<li>Fixed a bug that image caption was set z-index higher than toolbar.</li>
</ul>
<p>v2.17.0 (07/21/2019)</p>
<ul>
<li>The color picker has been modified to look natural when you change the color list.</li>
<li>Added &#8220;formats&#8221; option to change list of formatBlock.</li>
<li>Added &#8220;charCounter&#8221; option to show the current number of characters in the bottom bar.</li>
<li>Added &#8220;maxCharCount&#8221; option to limit the number of characters in the editor.</li>
<li>Added &#8220;minWidth&#8221; and &#8220;maxWidth&#8221; options.</li>
<li>When pasting images and videos, modified to convert to the format of the editor.</li>
<li>Modified to update the status of &#8220;imagesInfo&#8221; also when deleting an image by selecting a range.</li>
<li>Fixed a bug where the getContents method returned an empty value when there was no text in the editor.</li>
<li>Fixed a bug where pasted iframe tags did not resize correctly.</li>
<li>Fixed a bug that the history stack was not immediately updated when an image was deleted.</li>
<li>Fixed a bug where the link&#8217;s controller was rendered in the wrong position when the link was in the heading tag.</li>
<li>Fixed a bug where attributes of HTML were copied when adding text by dragging drop.</li>
<li>Modified to operate only backspace, delete, and enter keys when sizing module is active.</li>
<li>The getOffset method of the util object has been improved.</li>
</ul>
<p>v2.16.3 (07/06/2019)</p>
<ul>
<li>Replaced the zeroWidthSpace that was entered when creating an line with the br tag.</li>
<li>Added an activation effect to the FormatBlock list menu.</li>
<li>Added an activation effect to the List button.</li>
<li>Modified color selector design slightly.</li>
<li>Fixed a bug where table cells were selected when scrolling on mobile.</li>
<li>Fixed a bug that the return value of the getListChild and getListChildNodes methods of the util object contained br tags.</li>
<li>Fixed a bug that the max size, min size icons of the table controller did not display properly.</li>
<li>Added exception handling related to the br tag to nodeChange method of the core object.</li>
</ul>
<p>v2.16.2 (07/03/2019)</p>
<ul>
<li>Russian translation added.</li>
<li>The &#8220;print&#8221; and &#8220;preview&#8221; features has been improved.</li>
<li>Fixed a bug where the value of the &#8220;height&#8221; option was applied when the &#8220;minHeight&#8221; option value was entered as a numeric type.</li>
<li>The openWindowContents method has been removed from the core object and the print and preview methods have been added.</li>
<li>Deleted the code that calls notice when an error occurs during image upload. (Error message call during server upload is the same as before.)</li>
<li>Added getPageStyle method to util object</li>
</ul>
<p>v2.16.0 (06/30/2019)</p>
<ul>
<li>Update</li>
</ul>
<p>v2.15.3 (06/02/2019)</p>
<ul>
<li>Added imageUploadHeader option.</li>
<li>Fixed to remove the current line if there is image, video etc in the next line when using Delete key on blank line.</li>
<li>After selecting image and video components, component is moved down when enter key is pressed.</li>
<li>After selecting images and video components, the components are deleted when delete or backspace key is pressed.</li>
<li>When adding components such as table, image, and HR in the list, modified it to be added inside the LI tag.</li>
<li>Fixed to remove list and apply format tag when using format tag in list.</li>
<li>Fixed an error that was not working when editing the pasted image for the first time.</li>
<li>Fixed a bug where the enter key did not work when using pre, quotblock in figuare tag since v2.15.0.</li>
<li>Fixed bugs in list and format plugins and added exception handling.</li>
<li>Fixed a bug where the format tag inside was erased when the text node was modified when the format tag was in format tags such as p, div, h1-6, li.</li>
<li>And Several other bugs fixed.</li>
<li>Added insertComponent, getSelectedElementsAndComponents methods to core object.</li>
<li>Added ignoreNodeChange, getNodePath, getNodeFromPath methods to util object.</li>
<li>Updated core-nodeChange method.</li>
</ul>
<p>v2.15.2 (05/23/2019)</p>
<ul>
<li>French translation added.</li>
<li>A min size (auto), max size (100%) button has been added to the controller in the table.</li>
<li>The table controller has been modified to appear in two places, at the top of the table and in the cell.</li>
<li>When using a list, the list is merged if list is above or below it.</li>
<li>It has been modified to be added to the list when using hr in the list.</li>
<li>It has been modified to be added outside of the list when using pre, blockquote in the list.</li>
<li>When using pre, blockquote, it applies based on the outermost node in the current selection range.</li>
<li>Added maxSize and minSize entries to the Language file.</li>
<li>After adding a block, the action to add a format to the line below has been removed.</li>
<li>Fixed a bug that caused the toolbar to have a margin when using full screen in Sticky Toolbar mode.</li>
<li>Fixed bugs when in list adding and deleting items.</li>
<li>Fixed a bug where cells in tables were not displayed correctly intermittently.</li>
<li>Fixed a bug where the margin space was applied to the entire editor when using the tab key or indent key outside the table.</li>
<li>Fixed a bug where the list removal range was applied to all items in the list when the backspace key was pressed at the first end of the list.</li>
<li>Fixed a bug that was not recorded in history stack when using tab key.</li>
<li>Fixed a bug in the activation buttons.</li>
<li>Fixed a bug that removed blank lines when changing nodes after selecting all.</li>
<li>Fixed a bug to keydown event, node change.</li>
<li>The colorPicker module design has been modified.</li>
<li>The design of the resizing, link, table controllers has been modified.</li>
<li>The style of the Pre, blockquote tag has been modified.</li>
<li>Fixed an exception of browsers by setting scope directly in editor when using shortcutkey (CTRL + A).</li>
<li>Added removeItemAllParents, getElementDepth, isTable method of Util object.</li>
<li>Added color to title of the table.</li>
<li>Added isList and isListCell methods to Util object.</li>
<li>Modified the condition of format elements.</li>
<li>Modified correctly a regular expressions that can cause bugs.</li>
<li>Modified the util.convertHTMLForCodeView method.</li>
<li>Modified the core.detachRangeFormatElement, core.applyRangeFormatElement method.</li>
<li>Deleted getSelectedRangeFormatElements method of Util object.</li>
</ul>
<p>v2.14.0 (05/04/2019)</p>
<ul>
<li>modify: range format exit with enter</li>
<li>add: onChange function, button &#8211; disabled,</li>
</ul>
<p>v2.13.1 (04/23/2019)</p>
<ul>
<li>Updated undo and redo functions, which previously operated as &#8220;document.execCommand&#8221;.</li>
<li>Added getPositionIndex function to &#8220;util&#8221; object.</li>
<li>Fixed a bug &#8220;insertHTML&#8221; of user function.</li>
<li>Fixed a bug where format blocks, images and videos could not be deleted properly with backspace.</li>
<li>Modified the color and border radius of the editor.</li>
<li>Added &#8220;redo&#8221; and &#8220;undo&#8221; to buttons that are disabled in code view mode.</li>
<li>Added History object to &#8220;core&#8221; object for undo, redo.</li>
<li>Added onlyZeroWidthSpace function to &#8220;util&#8221; object.</li>
</ul>
<p>v2.13.0 (04/22/2019)</p>
<ul>
<li>Updated undo and redo functions, which previously operated as &#8220;document.execCommand&#8221;.</li>
<li>Added getPositionIndex function to &#8220;util&#8221; object.</li>
<li>Fixed a bug &#8220;insertHTML&#8221; of user function.</li>
<li>Modified the color and border radius of the editor.</li>
<li>Added &#8220;redo&#8221; and &#8220;undo&#8221; to buttons that are disabled in code view mode.</li>
<li>Added History object to &#8220;core&#8221; object for undo, redo.</li>
</ul>
<p>v2.12.4 (04/17/2019)</p>
<ul>
<li>Enhancement: The color picker module highlights the selected color.</li>
<li>Fixed a bug where the icon was not visible when adding custom buttons. customPlugin</li>
<li>Fixed a bug that did not resize the component&#8217;s cover when putting the description after resizing the image and video.</li>
</ul>
<p>v2.12.3 (03/22/2019)</p>
<ul>
<li>Fix a bug that the hex color input field of the colorPicker module was not working since version 2.9.4.</li>
<li>Fixed a bug where when using the dialog plugin after creating more than one editor, the results were applied to the editor that had the focus before.</li>
<li>Fixed Print did not work since version 2.8.3.</li>
<li>Fixed Inline, Balloon mode did not work properly since version 2.12.1.</li>
<li>Deleted the selected cell style in the table.</li>
<li>Added z-index to controllers</li>
</ul>
<p>v2.12.0 (03/21/2019)</p>
<ul>
<li>The language file has been modified to be added to the global object SUNEDITOR_LANG when used in HTML.</li>
<li>Added Chinese(zh_cn) translation.</li>
</ul>
<p>v2.11.0 (03/19/2019)</p>
<ul>
<li>Added youtubeQuery option &#8211; the query string of a YouTube embedded URL.</li>
</ul>
<p>v2.11.0 (03/17/2019)</p>
<ul>
<li>Added onImageUploadError event function &#8211; called when the image is upload failed.</li>
<li>Added argument imageInfo to onImageUpload function.</li>
<li>Added Japan translation.</li>
<li>Fixed a bug where the height value was fixed when adding the first image.</li>
</ul>
<p>v2.10.2 (03/14/2019)</p>
<ul>
<li>Fixed a bug where characters would disappear intermittently when changing nodes.</li>
<li>Added german translation.</li>
<li>Added save-button and &#8216;callBackSave&#8217; option.</li>
<li>Added src property to &#8216;imagesInfo&#8217;.</li>
</ul>
<p>v2.9.6 (02/07/2019)</p>
<ul>
<li>fix: btn-group style, focus bug</li>
<li>Fixed a bug that the focus was not updated in the editor when submenu and dialog menus were used.</li>
</ul>
<p>v2.9.5 (01/14/2019)</p>
<ul>
<li>fix: font size display</li>
</ul>
<p>v2.9.4 (12/14/2018)</p>
<ul>
<li>fix: node change function</li>
</ul>
<p>v2.9.3 (12/13/2018)</p>
<ul>
<li>modify: balloon editor event</li>
<li>fix: dialog submit, modify: submenu, controller off</li>
</ul>
<p>v2.9.2 (12/12/2018)</p>
<ul>
<li>fix: strike shortcut key, balloon editor ctrl + a</li>
</ul>
<p>v2.9.1 (12/11/2018)</p>
<ul>
<li>fix: node change &#8211; checked multi styles</li>
</ul>
<p>v2.9.0 (12/11/2018)</p>
<ul>
<li>fix: window event exception handling</li>
</ul>
<p>v2.8.5 (11/26/2018)</p>
<ul>
<li>fix: nodeChange exception</li>
<li>delete: styleWithCss</li>
</ul>
<p>v2.8.4 (11/22/2018)</p>
<ul>
<li>bugfix</li>
</ul>
<p>v2.8.3 (11/22/2018)</p>
<ul>
<li>update</li>
</ul>
<p>v2.8.2 (11/20/2018)</p>
<ul>
<li>fix: figcaption contenteditable</li>
</ul>
<p>v2.8.1 (11/15/2018)</p>
<ul>
<li>fix: node change, clipboard data</li>
</ul>
<p>v2.8.0 (11/11/2018)</p>
<ul>
<li>fix: rotate caption</li>
</ul>
<p>v2.7.0 (11/06/2018)</p>
<ul>
<li>fix: pre tag overflow</li>
</ul>
<p>v2.6.3 (11/05/2018)</p>
<ul>
<li>fix: code view &#8211; textarea</li>
<li>fix: percent size</li>
<li>fix: code view &#8211; textarea</li>
</ul>
<p>v2.6.1 (10/30/2018)</p>
<ul>
<li>fix: code view button blur</li>
<li>add option &#8211; resizingBar</li>
</ul>
<p>v2.5.3 (10/28/2018)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>v2.4.3 (10/25/2018)</p>
<ul>
<li>modify: util &#8211; getOffset, defaultCommand -&gt; commandHandler</li>
<li>fix: check description when editing image</li>
<li>modify: fullscreen icon</li>
</ul>
<p>v2.4.2 (10/19/2018)</p>
<ul>
<li>modify: button module, line break, button style</li>
<li>modify: indent function</li>
</ul>
<p>v2.4.0 (10/18/2018)</p>
<ul>
<li>added: resizing module &#8211; rotate</li>
<li>fix: default display</li>
<li>fix: editor object</li>
</ul>
<p>v2.3.2 (10/16/2018)</p>
<ul>
<li>fix: disabled, enabled, appendContents, setContents</li>
</ul>
<p>v2.3.1 (10/11/2018)</p>
<ul>
<li>fix: wrapToTags, appendContent, resizin, modify: preview, jshint</li>
</ul>
<p>v2.3.0 (10/08/2018)</p>
<ul>
<li>modify: arrow css</li>
</ul>
<p>v2.2.7 (10/04/2018)</p>
<ul>
<li>modify: button style, fix: contents style</li>
</ul>
<p>v2.2.6 (10/02/2018)</p>
<ul>
<li>fix: node change function &#8211; same node</li>
</ul>
<p>v2.2.5 (10/02/2018)</p>
<ul>
<li>fix: node change function</li>
</ul>
<p>v2.2.4 (09/28/2018)</p>
<ul>
<li>fix: image &#8211; xmlHttp, modify, loading box</li>
</ul>
<p>v2.2.2 (09/27/2018)</p>
<ul>
<li>fix: mobile event, submenu, callModule</li>
<li>fix: core &#8211; manage tags in delete operation</li>
<li>fix: getRange, image insert</li>
<li>fix: display option, tree shaking</li>
<li>update: dialog module split -&gt; dialog, resizing</li>
<li>fix: Applied &#8220;getFormElement&#8221; to indent and node function</li>
<li>fix: codeView, modify: codeView css, convertContentsForEditor, user func</li>
<li>update: button disabled in codeView, added hr icon, modify: contents.css</li>
<li>fix: font css of edit area</li>
<li>update: table cell controller, modify: language, fonts, css</li>
<li>fix: a tag css</li>
<li>fix: wrapRangeToTag</li>
</ul>
<p>v1.11.4 (08/31/2018)</p>
<ul>
<li>fix: context &#8211; delete originElementDisplay, added navigation</li>
</ul>
<p>v1.11.2 (08/29/2018)</p>
<ul>
<li>delete: SUNEDITOR.destroy</li>
</ul>
<p>v1.11.1 (08/29/2018)</p>
<ul>
<li>fix: wrapRangeToTag function</li>
</ul>
<p>v1.11.0 (08/28/2018)</p>
<ul>
<li>update: label path (showPathLabel), User function : getContext; fix: when format tag deleted, format tag check</li>
</ul>
<p>v1.10.4 (08/22/2018)</p>
<ul>
<li>fix: horizontalRules menu, _findButtonEffectTag performance</li>
</ul>
<p>v1.10.3 (08/22/2018)</p>
<ul>
<li>fix: svg-&gt;web font, button effects, optimizations</li>
</ul>
<p>v1.10.2 (08/17/2018)</p>
<ul>
<li>fix: video module</li>
</ul>
<p>v1.10.1 (08/17/2018)</p>
<ul>
<li>fix: Firefox, Opera window.event</li>
</ul>
<p>v1.10.0 (08/16/2018)</p>
<ul>
<li>update: image, video module &#8211; resizing, revert, constrain proportions</li>
</ul>
<p>v1.9.0 (08/12/2018)</p>
<ul>
<li>added show blocks button</li>
</ul>
<p>v1.8.2 (08/11/2018)</p>
<ul>
<li>update: module box line break,</li>
<li>fix: preivew viewport</li>
</ul>
<p>v1.7.2 (08/11/2018)</p>
<ul>
<li>update: subscript, superscript</li>
</ul>
<p>v1.6.2 (07/19/2018)</p>
<ul>
<li>fix: link openDialog</li>
</ul>
<p>v1.6.0 (07/16/2018)</p>
<ul>
<li>update: added image resize controller &#8211; left</li>
</ul>
<p>v1.4.0 (07/15/2018)</p>
<ul>
<li>update: formats</li>
</ul>
<p>v1.3.1 (07/14/2018)</p>
<ul>
<li>fix: setContent, getContent, added _convertContentForEditor function</li>
<li>fix: css z-index</li>
</ul>
<p>v1.3.0 (06/29/2018)</p>
<ul>
<li>update: added image &#8211; Alternative Text</li>
</ul>
<p>v1.2.4 (06/25/2018)</p>
<ul>
<li>fix: creating paragraph tags</li>
</ul>
<p>v1.2.3 (06/12/2018)</p>
<ul>
<li>bugfix.</li>
</ul>
<p>v1.2.0 (05/24/2018)</p>
<ul>
<li>more functionalities added.</li>
</ul>
<p>The post <a href="https://www.cssscript.com/minimal-wysiwyg-editor-pure-javascript-suneditor/">Minimal WYSIWYG Editor In Pure JavaScript &#8211; Suneditor</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/minimal-wysiwyg-editor-pure-javascript-suneditor/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4683</post-id>	</item>
		<item>
		<title>Two Direction Sticky Sidebar For Vanilla JS</title>
		<link>https://www.cssscript.com/two-direction-sticky-sidebar/</link>
					<comments>https://www.cssscript.com/two-direction-sticky-sidebar/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 05:07:59 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[sidebar]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=105280</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="two-direction-sticky-sidebar" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A vanilla JS sticky sidebar script with two way scroll behavior, live data attributes, and responsive mobile cutoff support.</p>
<p>The post <a href="https://www.cssscript.com/two-direction-sticky-sidebar/">Two Direction Sticky Sidebar For Vanilla JS</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="two-direction-sticky-sidebar" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/two-direction-sticky-sidebar.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>Two Direction Sticky Sidebar is a pure JavaScript utility that implements bi-directional sticky sidebar behavior in your web page:</p>
<ul>
<li>When the sidebar fits inside the viewport, it sticks at the top as expected.</li>
<li>When the sidebar content is taller than the viewport, it scrolls in sync with the user&#8217;s direction and locks to either the top or bottom edge of the screen once content runs out.</li>
</ul>
<h2>Features:</h2>
<ul>
<li>Creates a sticky sidebar for long content pages.</li>
<li>Supports two-way sidebar scroll behavior.</li>
<li>Keeps tall sidebar content accessible during page scroll.</li>
<li>Sets custom top spacing.</li>
<li>Sets custom bottom spacing.</li>
<li>Disables sticky behavior on small screens.</li>
<li>Updates sidebar positioning after live option changes.</li>
<li>Uses native browser APIs.</li>
<li>Supports legacy sticky markup.</li>
<li>Refreshes layout metrics after resize.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Include the Two Direction Sticky Sidebar&#8217;s JavaScript on the page.</p>
<pre>&lt;script src="sticky-sidebar.min.js"&gt;&lt;/script&gt;</pre>
<p>2. Add the <code>data-sticky-sidebar</code> attribute to your sidebar. The script queries for this attribute on initialization.</p>
<pre>&lt;aside data-sticky-sidebar&gt;
  &lt;nav&gt;
    &lt;a href="/articles"&gt;Articles&lt;/a&gt;
    &lt;a href="/tutorials"&gt;Tutorials&lt;/a&gt;
    &lt;a href="/tools"&gt;Tools&lt;/a&gt;
  &lt;/nav&gt;
&lt;/aside&gt;</pre>
<p>3. Pass configuration directly in HTML. All available HTML data attributes:</p>
<ul>
<li><strong><code>data-top-gap</code></strong> (integer or <code>"auto"</code>): Margin in pixels between the top of the viewport and the sidebar during sticky mode. Defaults to <code>0</code>. Pass <code>"auto"</code> to use the sidebar&#8217;s computed document offset on page load.</li>
<li><strong><code>data-bottom-gap</code></strong> (integer): Margin in pixels between the bottom of the viewport and the sidebar. Defaults to <code>0</code>.</li>
<li><strong><code>data-mobile-width</code></strong> (integer): Screen width in pixels at or below which the sticky effect turns off. Defaults to <code>0</code>, meaning sticky stays active at all viewport widths.</li>
</ul>
<pre>&lt;aside
  data-sticky-sidebar
  data-top-gap="120"
  data-bottom-gap="32"
  data-mobile-width="1024"&gt;
  &lt;!-- sidebar content --&gt;
&lt;/aside&gt;</pre>
<h2>Alternatives:</h2>
<ul>
<li><a href="https://www.jqueryscript.net/blog/best-sticky-sidebar.html" target="_blank" rel="noopener">10 Best Sticky Sidebar Plugins In JavaScript</a></li>
</ul>
<h2>FAQs:</h2>
<p><strong>Q: Does this library work with multiple sidebars on the same page?</strong><br />
A: No. The library works only the first matching element it finds.</p>
<p><strong>Q: The sidebar snaps to an incorrect position when the page loads. How do I fix this?</strong><br />
A: This usually happens when the sidebar sits below a fixed header. Set <code>data-top-gap="auto"</code> to tell the script to calculate the initial offset from the sidebar&#8217;s actual position in the document.</p>
<p><strong>Q: Can I update the gap values after the page has loaded?</strong><br />
A: Yes. Update <code>data-top-gap</code> or <code>data-bottom-gap</code> directly on the element via JavaScript. The MutationObserver inside the script detects attribute changes and reinitializes with the new values automatically.</p>
<p><strong>Q: Does the sticky behavior break inside CSS Grid or Flexbox layouts?</strong><br />
A: No. The script sets <code>position: sticky</code> and <code>height: fit-content</code> on the sidebar element. These apply correctly inside flex and grid containers as long as the sidebar&#8217;s parent container is taller than the sidebar itself.</p>
<p><strong>Q: What happens if <code>data-mobile-width</code> is not set?</strong><br />
A: Sticky behavior stays active at all viewport widths. Set it to a pixel value such as <code>1024</code> to disable the effect on screens narrower than that breakpoint.</p>
<p>The post <a href="https://www.cssscript.com/two-direction-sticky-sidebar/">Two Direction Sticky Sidebar For Vanilla JS</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/two-direction-sticky-sidebar/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">105280</post-id>	</item>
		<item>
		<title>Linked Lists with CSS Anchor Positioning</title>
		<link>https://www.cssscript.com/linked-lists-anchor-positioning/</link>
					<comments>https://www.cssscript.com/linked-lists-anchor-positioning/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 04:11:01 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=105275</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="linked-lists-anchor-positioning" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A CSS anchor-positioning pattern that draws two-segment connector lines between checked checkboxes and a selected radio button using pseudo-elements and container style queries.</p>
<p>The post <a href="https://www.cssscript.com/linked-lists-anchor-positioning/">Linked Lists with CSS Anchor Positioning</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="linked-lists-anchor-positioning" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/linked-lists-anchor-positioning.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>Linked Lists is a JavaScript &amp; CSS UI component that connects multiple checked list items to a selected radio option with dashed relation lines.</p>
<p>It&#8217;s ideal for settings panels, workflow maps, and form steps where the current choice needs a visible path back to its related items.</p>
<h2>Features:</h2>
<ul>
<li>Based on the native CSS anchor-positioning API.</li>
<li>Supports multiple simultaneous connections, each scoped independently to its own source element.</li>
<li>Draws two-segment connectors that route automatically based on whether the source sits above, below, or at the same vertical level as the target.</li>
<li>Full theming control through CSS custom properties for line color, stroke width, corner radius, and panel gap.</li>
<li>Position-aware connector geometry adjusts curve direction and corner rounding per segment for each positional case.</li>
<li>All connector layout and geometry run in CSS.</li>
<li>Automatic fallback message displays in legacy browsers.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Create two <code>&lt;ul&gt;</code> elements inside a shared container: one for checkboxes (<code>.items</code>) and one for radio buttons (<code>.options</code>).</p>
<pre>&lt;div id="linked-list" class="linked-list"&gt;
  &lt;!-- Checkbox list — multiple items can be checked simultaneously --&gt;
  &lt;ul class="list items" aria-labelledby="items-label"&gt;
    &lt;li&gt;
      &lt;label for="task-1"&gt;
        &lt;input type="checkbox" id="task-1" class="cb" value="1" checked&gt; Task Alpha
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="task-2"&gt;
        &lt;input type="checkbox" id="task-2" class="cb" value="2"&gt; Task Beta
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="task-3"&gt;
        &lt;input type="checkbox" id="task-3" class="cb" value="3" checked&gt; Task Gamma
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="task-4"&gt;
        &lt;input type="checkbox" id="task-4" class="cb" value="4"&gt; Task Delta
      &lt;/label&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
  &lt;!-- Radio list — only one option can be selected at a time --&gt;
  &lt;ul class="list options" role="radiogroup" aria-labelledby="options-label"&gt;
    &lt;li&gt;
      &lt;label for="phase-1"&gt;
        &lt;input type="radio" id="phase-1" name="phase" class="rb" value="1"&gt; Phase A
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="phase-2"&gt;
        &lt;input type="radio" id="phase-2" name="phase" class="rb" value="2"&gt; Phase B
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="phase-3"&gt;
        &lt;input type="radio" id="phase-3" name="phase" class="rb" value="3" checked&gt; Phase C
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="phase-4"&gt;
        &lt;input type="radio" id="phase-4" name="phase" class="rb" value="4"&gt; Phase D
      &lt;/label&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;label for="phase-5"&gt;
        &lt;input type="radio" id="phase-5" name="phase" class="rb" value="5"&gt; Phase E
      &lt;/label&gt;
    &lt;/li&gt;
  &lt;/ul&gt;
&lt;/div&gt;</pre>
<p>2. Add the CSS. The two anchoring rules are the heart of the pattern. <code>anchor-name: --checked-option</code> on the checked checkbox label registers it as the connector&#8217;s source point. <code>anchor-name: --radio-option</code> on the checked radio <code>li</code> registers it as the target. All segment edges are derived from the <code>anchor()</code> function referencing those two named points.</p>
<pre>@layer base, demo;

@layer demo {
  .linked-list {
    /* — Theming variables — adjust to match your design system — */
    --checkbox-checked-border-color: dodgerblue;
    --radio-checked-border-color: dodgerblue;
    --join-stroke: 1px;
    --join-line: var(--join-stroke) dashed dodgerblue; /* Connector line style */
    --join-radius: 20px;  /* Corner rounding on L-shaped bends */
    --gap: 10vw;          /* Horizontal gap between the two columns */

    position: relative;
    display: grid;
    align-items: start;
    grid-template-columns: 1fr 1fr; /* Two equal columns */
    width: min(100%, 800px);
    gap: var(--gap);
  }

  .list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: .5rem;
  }

  .list &gt; li {
    border: 1px solid var(--clr-lines);
    display: flex;
    align-items: center;
  }

  .list &gt; li &gt; label {
    flex: 1;
    padding: .5rem;
    cursor: pointer;
  }

  /* Checked checkbox label registers as the named anchor source */
  .list.items li:has(:checked) label {
    anchor-name: --checked-option;
    /* anchor-scope scopes this name to the local subtree so multiple
       checked checkboxes each get their own independent anchor chain */
    anchor-scope: --checked-option;
    border-color: var(--checkbox-checked-border-color);
  }

  /* First segment: spans from the right edge of the checkbox to the midpoint column */
  .list.items li:has(:checked) label::before,
  .list.items li:has(:checked) label::after {
    content: '';
    position: absolute;
    border: var(--join-line);
    right: calc(anchor(left --radio-option) + var(--gap) / 2);
    left: anchor(right --checked-option); /* Left edge aligns to checkbox right */
  }

  /* Second segment: spans from the midpoint column to the left edge of the radio option */
  .list.items li:has(:checked) label::after {
    right: anchor(left --radio-option);
    left: calc(anchor(left --radio-option) - var(--gap) / 2 - var(--join-stroke));
  }

  /* — Routing case 1: checkbox sits ABOVE the radio button — */
  @container style(--relation: -1) {
    .list.items li:has(:checked) label::before {
      border-left-color: transparent;
      border-bottom-color: transparent;
      border-radius: 0 var(--join-radius) 0 0; /* Rounds the top-right corner */
      top: anchor(center --checked-option);
      bottom: anchor(top --radio-option);
    }
    .list.items li:has(:checked) label::after {
      border-right-color: transparent;
      border-top-color: transparent;
      border-radius: 0 0 0 var(--join-radius); /* Rounds the bottom-left corner */
      top: calc(anchor(top --radio-option) - var(--join-stroke));
      bottom: anchor(center --radio-option);
    }
  }

  /* — Routing case 2: checkbox sits BELOW the radio button — */
  @container style(--relation: 1) {
    .list.items li:has(:checked) label::before {
      border-left-color: transparent;
      border-top-color: transparent;
      border-radius: 0 0 var(--join-radius) 0; /* Rounds the bottom-right corner */
      top: anchor(bottom --radio-option);
      bottom: anchor(center --checked-option);
    }
    .list.items li:has(:checked) label::after {
      border-right-color: transparent;
      border-bottom-color: transparent;
      border-radius: var(--join-radius) 0 0 0; /* Rounds the top-left corner */
      top: anchor(center --radio-option);
      bottom: calc(anchor(bottom --radio-option) - var(--join-stroke));
    }
  }

  /* — Routing case 3: checkbox and radio share the same vertical index — */
  @container style(--relation: 0) {
    .list.items li:has(:checked) label::before {
      /* Straight horizontal connector — no bends or radius needed */
      border-top-color: transparent;
      border-right-color: transparent;
      border-left-color: transparent;
      border-radius: 0;
      top: calc(anchor(center --radio-option) - var(--join-stroke));
      bottom: calc(anchor(center --checked-option) + var(--join-stroke));
      right: anchor(left --radio-option);
      left: anchor(right --checked-option);
    }
    .list.items li:has(:checked) label::after {
      display: none; /* Second segment is not needed for a straight line */
    }
  }

  /* Checked radio option registers as the named anchor target */
  .list.options li:has(:checked) {
    anchor-name: --radio-option;
    border-color: var(--radio-checked-border-color);
  }
}

/* — Base layer: general layout and color-scheme — */
@layer base {
  * { box-sizing: border-box; }

  :root {
    color-scheme: light dark;
    --bg-dark: rgb(16, 24, 40);
    --bg-light: rgb(248, 244, 238);
    --txt-light: rgb(10, 10, 10);
    --txt-dark: rgb(245, 245, 245);
    --line-light: rgba(0 0 0 / .25);
    --line-dark: rgba(255 255 255 / .25);

    /* light-dark() picks the first value in light mode, second in dark mode */
    --clr-bg: light-dark(var(--bg-light), var(--bg-dark));
    --clr-txt: light-dark(var(--txt-light), var(--txt-dark));
    --clr-lines: light-dark(var(--line-light), var(--line-dark));
  }

  body {
    background-color: var(--clr-bg);
    color: var(--clr-txt);
    min-height: 100svh;
    margin: 0;
    padding: 2rem;
    font-family: "Jura", sans-serif;
    display: grid;
    place-content: center;
    gap: 2rem;
  }

  /* Fallback notice for browsers without anchor-positioning support */
  @supports not (position-anchor: --test) {
    body::before {
      content: "Your browser does not support CSS anchor-positioning";
      position: fixed;
      top: 2rem;
      left: 50%;
      translate: -50% 0;
      font-size: 0.8rem;
    }
  }
}</pre>
<p>3. Determine whether each checkbox row sits above, below, or at the same index as the currently selected radio option, then write that relationship as a CSS custom property.</p>
<pre>const root = document.querySelector('.linked-list');

// Maps the three possible Math.sign() outputs to CSS --relation values
// Index 0 = above (-1), Index 1 = same (0), Index 2 = below (1)
const REL = ['-1', '0', '1'];

function updateRelations() {
  // Find the currently selected radio option row
  const radioLi = root.querySelector('.options li:has(input:checked)');
  if (!radioLi) return;

  // Get the zero-based position of that row in the radio list
  const radioIndex = [...radioLi.parentElement.children].indexOf(radioLi);

  // Write --relation to every checkbox row based on its position vs. the radio
  root.querySelectorAll('.items li').forEach((li, i) =&gt; {
    // Math.sign returns -1 (above), 0 (same level), or 1 (below)
    // Adding 1 shifts the result to 0, 1, or 2 for array indexing
    li.style.setProperty('--relation', REL[Math.sign(i - radioIndex) + 1]);
  });
}

// Re-run on any radio or checkbox change inside the container
root.addEventListener('change', updateRelations);

// Set the initial --relation values on page load
updateRelations();</pre>
<p>The post <a href="https://www.cssscript.com/linked-lists-anchor-positioning/">Linked Lists with CSS Anchor Positioning</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/linked-lists-anchor-positioning/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">105275</post-id>	</item>
		<item>
		<title>Modern Image Crop Web Component &#8211; VanillaJCrop</title>
		<link>https://www.cssscript.com/image-crop-web-component/</link>
					<comments>https://www.cssscript.com/image-crop-web-component/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 02:40:44 +0000</pubDate>
				<category><![CDATA[Image]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[image cropping]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=105094</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="image-crop-web-component" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A modern ES6+ image cropping library that ships as a Web Component and a headless API, with touch input, aspect ratio locking, and full CSS theming.</p>
<p>The post <a href="https://www.cssscript.com/image-crop-web-component/">Modern Image Crop Web Component &#8211; VanillaJCrop</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="image-crop-web-component" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/image-crop-web-component.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>VanillaJCrop is an image cropping library that replaces the classic <a href="https://www.jqueryscript.net/other/Image-Cropping-Library-Jcrop.html" target="_blank" rel="noopener">JCrop jQuery plugin</a> in pure ES6+ JavaScript.</p>
<p>It comes with two integration modes: a declarative <code>&lt;jcrop-widget&gt;</code> Web Component and a programmatic headless API.</p>
<p>Both modes handle mouse, touch, and pen input through the Pointer Events API, and the component scopes all its styles inside Shadow DOM to prevent conflicts with your page CSS.</p>
<h2>Features:</h2>
<ul>
<li>No external dependencies.</li>
<li>A native Web Component for HTML-attribute-based configuration.</li>
<li>A headless API that separates core crop logic from the rendering layer.</li>
<li>Handles mouse, touch, and pen input through the Pointer Events API.</li>
<li>Keyboard navigation moves selections with arrow keys and cancels with Escape.</li>
<li>Aspect ratio locking constrains crop areas to any fixed ratio, such as 16:9, 4:3, or 1:1.</li>
<li>Minimum and maximum dimension constraints keep selections within defined pixel bounds.</li>
<li>Animated transitions move the selection box smoothly from one position to another.</li>
<li>Full appearance customization through CSS custom properties on the component element.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Download the package and import it as a local module.</p>
<pre>&lt;script type="module"&gt;
  import JCrop, { JCropWidget } from './src/index.js';
&lt;/script&gt;</pre>
<p>2. Add the <code>&lt;jcrop-widget&gt;</code> custom element to your HTML with the <code>src</code> attribute pointing to your image. Constraints like <code>ratio</code> and <code>min-width</code> go directly on the element as attributes:</p>
<ul>
<li><strong><code>src</code></strong> (string): Image source URL. Example: <code>src="photo.jpg"</code>.</li>
<li><strong><code>ratio</code></strong> (number | fraction): Aspect ratio for the selection. Accepts a number or fraction string. Example: <code>ratio="16/9"</code> or <code>ratio="1.5"</code>.</li>
<li><strong><code>min-width</code></strong> (number): Minimum selection width in pixels. Example: <code>min-width="100"</code>.</li>
<li><strong><code>min-height</code></strong> (number): Minimum selection height in pixels. Example: <code>min-height="100"</code>.</li>
<li><strong><code>max-width</code></strong> (number): Maximum selection width in pixels. Example: <code>max-width="500"</code>.</li>
<li><strong><code>max-height</code></strong> (number): Maximum selection height in pixels. Example: <code>max-height="500"</code>.</li>
<li><strong><code>disabled</code></strong> (boolean): Disables all interaction. Example: <code>disabled</code>.</li>
<li><strong><code>selection</code></strong> (string): Sets the initial selection in image coordinates as <code>x,y,x2,y2</code>. Example: <code>selection="100,50,400,300"</code>.</li>
<li><strong><code>grid</code></strong> (boolean): Shows a rule-of-thirds grid inside the selection. Example: <code>grid</code>.</li>
<li><strong><code>crosshair</code></strong> (boolean): Shows a center crosshair inside the selection. Example: <code>crosshair</code>.</li>
<li><strong><code>no-move</code></strong> (boolean): Locks the selection position and disables dragging. Example: <code>no-move</code>.</li>
<li><strong><code>no-resize</code></strong> (boolean): Locks the selection size, hides resize handles, and disables drawing a new selection. Example: <code>no-resize</code>.</li>
<li><strong><code>outside-click</code></strong> (string): Controls what happens when the user clicks outside the selection. Use <code>release</code> to clear the selection or <code>ignore</code> to keep it unchanged. Default: <code>release</code>. Example: <code>outside-click="ignore"</code>.</li>
</ul>
<pre>&lt;jcrop-widget
  src="portrait.jpg"
  ratio="4/3"
  min-width="80"&gt;
&lt;/jcrop-widget&gt;</pre>
<pre>&lt;script type="module"&gt;
  import { JCropWidget } from './src/index.js';

  const cropper = document.querySelector('jcrop-widget');

  // Fires each time the user finalizes a crop selection
  cropper.addEventListener('crop-select', (e) =&gt; {
    // e.detail returns coordinates in actual image pixels
    const { x, y, w, h } = e.detail;
    console.log(`Crop: ${w}x${h} starting at (${x}, ${y})`);
  });

  // Fires continuously as the user drags
  cropper.addEventListener('crop-change', (e) =&gt; {
    console.log('Live selection:', e.detail);
  });

  // Fires when the crop selection is cleared
  cropper.addEventListener('crop-release', () =&gt; {
    console.log('No active selection');
  });
&lt;/script&gt;</pre>
<p>3. You can also control the widget through its JavaScript API. Grab the element by reference and call methods directly:</p>
<pre>const widget = document.querySelector('jcrop-widget');

// Read the current selection as { x, y, w, h, x2, y2 } in image coordinates
console.log(widget.value);

// Set a new selection immediately
widget.setSelection({ x: 40, y: 40, x2: 380, y2: 290 });

// Animate to a new selection; optional callback fires on completion
widget.animateTo({ x: 100, y: 80, x2: 520, y2: 400 }, () =&gt; {
  console.log('Transition complete');
});

// Clear the active selection
widget.release();

// Destroy the widget and remove all event listeners
widget.destroy();</pre>
<p>4. The headless API is the right choice when you need crop logic on an existing DOM container or inside a custom canvas pipeline. Pass your element and a configuration object to the <code>JCrop</code> constructor. The <code>imageWidth</code> and <code>imageHeight</code> options map display coordinates back to actual image pixels, so read them from the image element rather than hardcoding.</p>
<pre>import JCrop from './src/index.js';

// Attach JCrop to a container element
const jcrop = new JCrop(document.getElementById('crop-stage'), {
  canvasWidth: 720,    // display width of the rendered crop area
  canvasHeight: 480,   // display height of the rendered crop area
  imageWidth: 2160,    // actual pixel width of the source image
  imageHeight: 1440,   // actual pixel height of the source image
  ratio: 3 / 2,        // lock to a 3:2 aspect ratio
  minWidth: 120,
  minHeight: 80,
  maxWidth: Infinity,
  maxHeight: Infinity,
  fadeTime: 400,
  handleWidth: 10,
  handleHeigh: 10,
  onChange: (coords) =&gt; {
    // Fires on every drag move during selection
    console.log('In progress:', coords);
  },
  onSelect: (coords) =&gt; {
    // Fires when the user releases the pointer
    console.log('Final selection:', coords);
    submitCropToServer(coords);
  },
  onRelease: () =&gt; {
    console.log('Selection cleared');
  }
});

// Jump the selection to a position immediately
jcrop.setSelect({ x: 60, y: 40, x2: 420, y2: 320 });

// Animate the selection to a new position
jcrop.animateTo({ x: 100, y: 70, x2: 520, y2: 410 });

// Read the current selection in image coordinates
const imageCoords = jcrop.tellSelect();

// Read the current selection in canvas (display) coordinates
const displayCoords = jcrop.tellScaled();

// Update options at runtime — useful for toggling aspect ratio on the fly
jcrop.setOptions({ ratio: 1, minWidth: 150 });

// Clear the selection
jcrop.release();

// Tear down the instance and remove all listeners
jcrop.dispose();</pre>
<p>5. API configuration options:</p>
<ul>
<li><strong><code>canvasWidth</code></strong> (number): Display width of the crop area in pixels. Defaults to <code>800</code>.</li>
<li><strong><code>canvasHeight</code></strong> (number): Display height of the crop area in pixels. Defaults to <code>600</code>.</li>
<li><strong><code>imageWidth</code></strong> (number): Actual pixel width of the source image. Defaults to <code>1600</code>.</li>
<li><strong><code>imageHeight</code></strong> (number): Actual pixel height of the source image. Defaults to <code>1200</code>.</li>
<li><strong><code>ratio</code></strong> (number | null): Locked aspect ratio as a width-to-height number. Pass <code>null</code> for a free selection. Defaults to <code>null</code>.</li>
<li><strong><code>minWidth</code></strong> (number): Minimum selection width in pixels. Defaults to <code>50</code>.</li>
<li><strong><code>minHeight</code></strong> (number): Minimum selection height in pixels. Defaults to <code>50</code>.</li>
<li><strong><code>maxWidth</code></strong> (number): Maximum selection width in pixels. Defaults to <code>Infinity</code>.</li>
<li><strong><code>maxHeight</code></strong> (number): Maximum selection height in pixels. Defaults to <code>Infinity</code>.</li>
<li><strong><code>fadeTime</code></strong> (number): Duration of animated transitions in milliseconds. Defaults to <code>400</code>.</li>
<li><strong><code>handleWidth</code></strong> (number): Width of resize handles in pixels. Defaults to <code>10</code>.</li>
<li><strong><code>handleHeight</code></strong> (number): Height of resize handles in pixels. Defaults to <code>10</code>.</li>
<li><strong><code>onChange</code></strong> (function): Callback that fires on every selection change. Receives a coordinates object.</li>
<li><strong><code>onSelect</code></strong> (function): Callback that fires when the selection is finalized. Receives a coordinates object.</li>
<li><strong><code>onRelease</code></strong> (function): Callback that fires when the selection is cleared. Receives no arguments.</li>
</ul>
<p>6. API methods.</p>
<pre>// Set the crop selection immediately; accepts { x, y, x2, y2 } in image pixels
jcrop.setSelect({ x: 80, y: 60, x2: 480, y2: 360 });

// Animate the selection to new coordinates
// Optional second argument is a callback fired when the animation ends
jcrop.animateTo({ x: 120, y: 90, x2: 560, y2: 410 }, () =&gt; {
  console.log('Animation finished');
});

// Return the current selection in image coordinates: { x, y, w, h, x2, y2 }
const imageCoords = jcrop.tellSelect();

// Return the current selection in canvas (display) coordinates
const displayCoords = jcrop.tellScaled();

// Clear the active selection
jcrop.release();

// Check whether an animation is currently running; returns a boolean
const active = jcrop.isAnimating();

// Stop any running animation at its current position
jcrop.cancelAnimation();

// Update one or more options at runtime; changes take effect immediately
jcrop.setOptions({ ratio: 16 / 9, minWidth: 200 });

// Destroy the instance and remove all event listeners from the DOM
jcrop.dispose();</pre>
<p>7. Web component events.</p>
<pre>const widget = document.querySelector('jcrop-widget');

// Fires continuously as the user drags; useful for live coordinate previews
// e.detail: { x, y, w, h, x2, y2 } in image coordinates
widget.addEventListener('crop-change', (e) =&gt; {
  console.log('Dragging:', e.detail);
});

// Fires once when the user releases the pointer and the selection is confirmed
// e.detail: { x, y, w, h, x2, y2 } in image coordinates
widget.addEventListener('crop-select', (e) =&gt; {
  console.log('Confirmed crop:', e.detail);
});

// Fires when the current selection is cleared programmatically or by the user
widget.addEventListener('crop-release', () =&gt; {
  console.log('Selection removed');
});

// Fires on errors; most common cause is a failed image load
// e.detail: { error }
widget.addEventListener('crop-error', (e) =&gt; {
  console.error('Load failure:', e.detail.error);
});</pre>
<p>8. Customize the cropper with the following CSS variables.</p>
<pre>jcrop-widget {
  /* Translucent shade overlay outside the selection */
  --jcrop-shade-color: rgba(0, 0, 0, 0.6);

  /* Selection border */
  --jcrop-border-color: #ffffff;
  --jcrop-border-width: 2px;
  --jcrop-border-style: solid;

  /* Resize handle appearance */
  --jcrop-handle-size: 14px;
  --jcrop-handle-color: #ffffff;
  --jcrop-handle-border: 2px solid #1a1a1a;
  --jcrop-handle-radius: 3px;
}</pre>
<p>9. Migrating from JCrop jQuery plugin:</p>
<pre>// ---- JCrop (jQuery) ----
$('#photo').Jcrop({
  onSelect: function(c) { console.log(c); },
  aspectRatio: 1.5
});
api.setSelect([80, 60, 480, 360]);       // array format
api.animateTo([120, 90, 560, 410]);      // array format
api.tellSelect();
api.release();
api.destroy();

// ---- VanillaJCrop (ES6+) ----
const jcrop = new JCrop(document.getElementById('photo'), {
  onSelect: (c) =&gt; console.log(c),
  ratio: 1.5
});
jcrop.setSelect({ x: 80, y: 60, x2: 480, y2: 360 });   // object format
jcrop.animateTo({ x: 120, y: 90, x2: 560, y2: 410 });   // object format
jcrop.tellSelect();
jcrop.release();
jcrop.dispose(); // renamed from destroy()</pre>
<h2>Alternatives:</h2>
<ul>
<li><a href="https://www.jqueryscript.net/blog/best-image-croppers.html" target="_blank" rel="noopener">10 Best Image Croppers In jQuery And Vanilla JavaScript</a></li>
</ul>
<h2>Changelog:</h2>
<p>v0.2.2 (04/20/2026)</p>
<ul>
<li>Bugfixes</li>
</ul>
<p>The post <a href="https://www.cssscript.com/image-crop-web-component/">Modern Image Crop Web Component &#8211; VanillaJCrop</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/image-crop-web-component/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">105094</post-id>	</item>
		<item>
		<title>UX-First CSS Foundation for Modern Web &#8211; Browserux.css</title>
		<link>https://www.cssscript.com/css-foundation-browserux/</link>
					<comments>https://www.cssscript.com/css-foundation-browserux/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Tue, 21 Apr 2026 02:40:10 +0000</pubDate>
				<category><![CDATA[CSS & CSS3]]></category>
		<category><![CDATA[css reset]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=95659</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="css-foundation-browserux" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A modern CSS reset that improves usability, accessibility, and visual consistency across devices without requiring utility classes or JavaScript.</p>
<p>The post <a href="https://www.cssscript.com/css-foundation-browserux/">UX-First CSS Foundation for Modern Web &#8211; Browserux.css</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="css-foundation-browserux" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2025/05/css-foundation-browserux.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>browserux.css is a minimal CSS library that enhances native HTML and CSS behaviors into a clean, modern baseline for improved usability and accessibility. Think of it as a thoughtful evolution of `normalize.css`, built with current UX and accessibility best practices in mind.</p>
<p>The main idea here is to address those initial browser inconsistencies and enhance the user experience from the ground up. It tackles things like default form styling, scrollbar appearance, text selection, and focus states, all while respecting user preferences like dark mode or reduced motion.</p>
<h2>Features:</h2>
<ul>
<li><strong>CSS Reset:</strong> A targeted reset that handles margins, paddings, <code>box-sizing</code>, and sensible defaults for lists, buttons, and media.</li>
<li><strong>Cross-Browser Normalization:</strong> Standardizes typography, form elements, and media rendering for a more consistent look across modern browsers.</li>
<li><strong>Enhanced Usability:</strong> Implements smooth scrolling (respecting user preferences), optimizes mobile tap handling with <code>touch-action: manipulation</code>, and ensures scrollbars are always visible to prevent layout jank.</li>
<li><strong>Accessibility First:</strong> Natively supports <code>prefers-color-scheme</code> (dark mode), <code>prefers-reduced-motion</code>, and provides clear, keyboard-friendly <code>:focus-visible</code> states.</li>
<li><strong>Themeable via CSS Variables:</strong> Leverages CSS custom properties extensively, making it straightforward to customize colors, typography, scrollbar styles, and more.</li>
<li><strong>Styled Native Elements:</strong> Provides improved styling for text selection, scrollbars (WebKit &amp; Firefox), form controls including validation states, and progress bars using native CSS.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Install browserux.css and import it into your project.</p>
<pre># NPM
$ npm install browserux.css</pre>
<pre>// In your main JS file (e.g., main.js or app.js)
import 'browserux.css/browserux.css';

/* Or in your main CSS file */
@import 'browserux.css/browserux.css';

&lt;!-- Or in your HTML file --&gt;
&lt;link rel="stylesheet" href="/path/to/your/css/browserux.min.css"&gt;</pre>
<p>2. You can easily create your own themes by overriding the default CSS variables.</p>
<pre>:root {
  /* Global colors */
  --ui-page-bg: #eaeaea;
  --ui-page-color: #121212;
  --ui-color-primary: #f05e0e;
  --ui-color-secondary: #0e93f0;
  --ui-transparent: transparent;

  /* Form validation colors */
  --ui-valid-border-color: #29b94c;
  --ui-valid-bg-color: #f0fff5;
  --ui-invalid-border-color: #dc303e;
  --ui-invalid-bg-color: #fff0f0;
  --ui-placeholder-color: #aaa;
  --ui-invalid-placeholder-color: #dc303e;
  
  /* Progress bar colors */
  --ui-progress-bar-bg: #efefef;
  --ui-progress-value-bg: #29b94c;

  /* Text selection */
  --ui-selection-bg: var(--ui-page-color);
  --ui-selection-color: var(--ui-page-bg);
  --ui-selection-text-shadow: none;

  /* Scrollbar */
  --ui-scrollbar: var(--ui-page-bg);
  --ui-scrollbar-track: #ddecf6;
  --ui-scrollbar-thumb: var(--ui-color-secondary);
  --ui-scrollbar-thumb-hover: var(--ui-color-primary);
  --ui-scrollbar-vertical-width: 10px;
  --ui-scrollbar-horizontal-height: 10px;

  /* Typography */
  --ui-typo-font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
  --ui-typo-mono-font-family: ui-monospace, SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace;
  --ui-typo-font-size: 1.6rem;
  --ui-typo-line-height: 1.6;
}</pre>
<h2>Comparison with Alternatives</h2>
<p><strong><code>normalize.css</code>:</strong> <code>normalize.css</code> aims for consistent browser defaults while preserving useful ones. <code>browserux.css</code> builds on this philosophy by <em>actively enhancing</em> these defaults for better UX and accessibility. It&#8217;s more opinionated about scrollbars, focus states, and user preferences. If you want the absolute minimum intervention, <code>normalize.css</code> is a solid choice. If you appreciate a more polished starting point out-of-the-box, <code>browserux.css</code> is a step further.</p>
<p><strong>Traditional <code>reset.css</code> :</strong> Resets are typically more aggressive, stripping nearly all default browser styling to provide a &#8220;blank slate.&#8221; This means you have to define everything from scratch. <code>browserux.css</code> is less destructive; it refines rather than obliterates. For projects where you want to leverage and improve upon sensible browser defaults, <code>browserux.css</code> is a better fit than a hard reset.</p>
<p><strong>Tailwind CSS Preflight:</strong> Preflight, which is part of Tailwind CSS, is also built upon <code>normalize.css</code> and provides a set of base styles. However, Preflight is designed to work seamlessly within the Tailwind utility-first ecosystem. <code>browserux.css</code> is framework-agnostic, much lighter, and focuses purely on enhancing native browser elements without introducing a utility-class system or any opinions on how you should build components.</p>
<h2>FAQs</h2>
<p><strong>Q: Is <code>browserux.css</code> a full UI framework like Bootstrap or Materialize?</strong><br />
A: No, not at all. <code>browserux.css</code> is a foundational CSS layer. It improves the default styling and behavior of native HTML elements. You&#8217;d typically use it as a starting point, before or alongside more comprehensive styling solutions or your custom CSS. It doesn&#8217;t provide pre-built components or utility classes.</p>
<p><strong>Q: How does <code>browserux.css</code> handle dark mode if I have my own JavaScript-based theme switcher?</strong><br />
A: <code>browserux.css</code> uses the <code>prefers-color-scheme: dark</code> CSS media query to automatically adjust its internal CSS variables for a dark theme. If your JS theme switcher modifies these same CSS variables (e.g., <code>--ui-page-bg</code>, <code>--ui-page-color</code>) or your own set of theme variables that <code>browserux.css</code> might inherit from, they can work together. The key is that <code>browserux.css</code> respects the OS-level preference by default. You can always override its variables with higher specificity or directly via JS if your switcher manipulates styles that way.</p>
<p><strong>Q: What if I disagree with some of its opinions, like the always-visible scrollbar?</strong><br />
A: Most visual aspects, like scrollbar width or colors, are controlled by CSS variables (e.g., <code>--ui-scrollbar-vertical-width</code>). You can override these in your own stylesheet. For behavioral styles like <code>overflow-y: scroll;</code> on the <code>html</code> element, you&#8217;d need a CSS rule with equal or higher specificity in your stylesheet, for example, <code>html { overflow-y: auto; }</code>. It’s generally quite straightforward to tweak.</p>
<p><strong>Q: Will <code>browserux.css</code> conflict with my project&#8217;s existing CSS?</strong><br />
A: It&#8217;s designed as a base layer, so its selectors are mostly for raw HTML elements and have relatively low specificity. Conflicts are possible if your existing CSS also styles basic elements without classes or with similarly low specificity. Generally, your own class-based styles or more specific selectors should override <code>browserux.css</code> without issues. I&#8217;d recommend loading <code>browserux.css</code> before your custom stylesheets.</p>
<p><strong>Q: Why does it set <code>font-size: 62.5%</code> on the <code>html</code> element?</strong><br />
A: This is a common technique. Assuming the browser&#8217;s default font size is <code>16px</code>, setting <code>font-size: 62.5%</code> on <code>html</code> makes <code>1rem</code> equal to <code>10px</code> (<code>16px * 0.625 = 10px</code>). This can simplify converting <code>px</code> values to <code>rem</code> units in your head during development. The actual body font size is then set using <code>var(--ui-typo-font-size)</code>, which defaults to <code>1.6rem</code> (translating to <code>16px</code> in this setup).</p>
<h2>Changelog:</h2>
<p>v4.0.0 (04/20/2026)</p>
<ul>
<li>Major update</li>
</ul>
<p>v3.0.0 (04/01/2026)</p>
<ul>
<li>Update</li>
</ul>
<p>v2.0.0 (06/07/2025)</p>
<ul>
<li>To improve project scalability and avoid naming collisions with other libraries or design systems, all CSS variables previously prefixed with &#8211;ui- have been renamed to &#8211;bux-.</li>
</ul>
<p>v1.0.2 (05/12/2025)</p>
<ul>
<li>Minor fixes and cleanup</li>
</ul>
<p>The post <a href="https://www.cssscript.com/css-foundation-browserux/">UX-First CSS Foundation for Modern Web &#8211; Browserux.css</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/css-foundation-browserux/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">95659</post-id>	</item>
		<item>
		<title>Self-Hosted WYSIWYG Editor in Vanilla JS &#8211; Neiki Editor</title>
		<link>https://www.cssscript.com/wysiwyg-editor-neiki-editor/</link>
					<comments>https://www.cssscript.com/wysiwyg-editor-neiki-editor/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 05:32:42 +0000</pubDate>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Text]]></category>
		<category><![CDATA[editor]]></category>
		<category><![CDATA[WYSIWYG Editor]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=105249</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="wysiwyg-editor-neiki-editor" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A lightweight WYSIWYG editor with full toolbar customization, dark mode, i18n support for 8 languages, and a plugin API. React, Vue, and PHP ready.</p>
<p>The post <a href="https://www.cssscript.com/wysiwyg-editor-neiki-editor/">Self-Hosted WYSIWYG Editor in Vanilla JS &#8211; Neiki Editor</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="wysiwyg-editor-neiki-editor" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/wysiwyg-editor-neiki-editor.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>Neiki Editor is a vanilla JavaScript rich text editor that turns a textarea into a full-featured WYSIWYG HTML editor with a single JS call.</p>
<p>It supports configurable toolbars, light and dark themes, built-in localization for eight languages, and a plugin registration API for custom toolbar extensions.</p>
<h2>Features:</h2>
<ul>
<li>Fully configurable toolbar with support for button ordering, group separators, and responsive wrapping on smaller screens.</li>
<li>Light and dark theme support with automatic persistence across page reloads.</li>
<li>Built-in localization for English, Czech, Chinese, Spanish, German, French, Portuguese, and Japanese.</li>
<li>Custom translation support for any language via static registration or inline config.</li>
<li>Find and Replace with case-sensitive search and regular expression support.</li>
<li>Floating toolbar appears above selected text for quick inline formatting access.</li>
<li>Table insertion with a right-click context menu for adding rows, columns, and merging or splitting cells.</li>
<li>Image insertion by URL, file upload, or direct drag-and-drop into the editor area.</li>
<li>Autosave to localStorage with debounced writes, status bar feedback, and restore on page reload.</li>
<li>Plugin API for registering custom toolbar buttons with SVG icons, tooltips, and initialization callbacks.</li>
<li>HTML source view toggle for switching between visual mode and raw markup editing.</li>
<li>Status bar displays live word count, character count, autosave state, and current block type.</li>
<li>PHP integration helper with asset loading, editor rendering, and server-side HTML sanitization methods.</li>
<li>Content export as HTML file download and browser print dialog support.</li>
</ul>
<h2>Use Cases:</h2>
<ul>
<li>CMS content editors that need a self-hosted rich text field with no reliance on third-party services.</li>
<li>Blog platforms that need a configurable toolbar scoped to specific formatting options per post type.</li>
<li>Admin dashboards that require structured HTML content authoring with table editing support.</li>
<li>PHP web applications that need a server-side render helper and sanitization layer for form-submitted HTML.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Load the Neiki Editor&#8217;s JavaScript and stylesheet:</p>
<p>&lt;!&#8211; Load the editor stylesheet &#8211;&gt;<br />
&lt;link rel=&#8221;stylesheet&#8221; href=&#8221;https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css&#8221;&gt;</p>
<p>&lt;!&#8211; Load the editor script &#8211;&gt;<br />
&lt;script src=&#8221;https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js&#8221;&gt;&lt;/script&gt;</p>
<p>2. Add a textarea to your webpage.</p>
<pre>&lt;textarea id="editor"&gt;
  &lt;h2&gt;Weekly Product Digest&lt;/h2&gt;
  &lt;p&gt;Write a polished update for the team.&lt;/p&gt;
&lt;/textarea&gt;</pre>
<p>3. Initialize Neiki Editor with default options.</p>
<pre>const editor = new NeikiEditor('#editor');</pre>
<p>4. Cofigure your editor with the following options:</p>
<ul>
<li><strong><code>placeholder</code></strong> (string): Placeholder text displayed when the editor contains no content. Default: <code>'Start typing...'</code>.</li>
<li><strong><code>minHeight</code></strong> (number): Minimum editor height in pixels. Default: <code>300</code>.</li>
<li><strong><code>maxHeight</code></strong> (number|null): Maximum editor height in pixels. The editor adds vertical scrolling when content exceeds this value. Default: <code>null</code>.</li>
<li><strong><code>autofocus</code></strong> (boolean): Moves keyboard focus to the editor on initialization. Default: <code>false</code>.</li>
<li><strong><code>spellcheck</code></strong> (boolean): Activates the browser&#8217;s built-in spellcheck on editor content. Default: <code>true</code>.</li>
<li><strong><code>readonly</code></strong> (boolean): Puts the editor into read-only mode on load. Default: <code>false</code>.</li>
<li><strong><code>theme</code></strong> (string): Sets the initial color theme. Accepts <code>'light'</code> or <code>'dark'</code>. Default: <code>'light'</code>.</li>
<li><strong><code>language</code></strong> (string): Sets the UI language. Accepts <code>'en'</code>, <code>'cs'</code>, <code>'zh'</code>, <code>'es'</code>, <code>'de'</code>, <code>'fr'</code>, <code>'pt'</code>, or <code>'ja'</code>. Default: <code>'en'</code>.</li>
<li><strong><code>translations</code></strong> (object|null): Custom translation key map merged on top of the selected built-in language pack. Default: <code>null</code>.</li>
<li><strong><code>toolbar</code></strong> (array): Controls which toolbar buttons appear and in what order. Insert the string <code>'|'</code> at any position to add a visual group separator.</li>
<li><strong><code>onChange</code></strong> (function|null): Callback invoked on every content change. Receives <code>(content, editor)</code> where <code>content</code> is the current HTML string. Default: <code>null</code>.</li>
<li><strong><code>onSave</code></strong> (function|null): Callback invoked when the user triggers Save via Ctrl+S or the More menu. Receives <code>(content, editor)</code>. Default: <code>null</code>.</li>
<li><strong><code>onFocus</code></strong> (function|null): Callback invoked when the editor gains keyboard focus. Receives <code>(editor)</code>. Default: <code>null</code>.</li>
<li><strong><code>onBlur</code></strong> (function|null): Callback invoked when the editor loses focus. Receives <code>(editor)</code>. Default: <code>null</code>.</li>
<li><strong><code>onReady</code></strong> (function|null): Callback invoked once after the editor finishes initializing. Receives <code>(editor)</code>. Default: <code>null</code>.</li>
<li><strong><code>showHelp</code></strong> (boolean): Shows or hides the Help entry in the More menu (<code>⋯</code>). Default: <code>true</code>.</li>
</ul>
<pre>const editor = new NeikiEditor('#editor', {

  // Placeholder text shown when the editor is empty
  placeholder: 'Write your article content here...',

  // Minimum editor height in pixels
  minHeight: 400,

  // Maximum height before scrolling kicks in (null = no limit)
  maxHeight: 800,

  // Focus the editor immediately after load
  autofocus: false,

  // Enable browser-native spellcheck
  spellcheck: true,

  // Set to true to block all editing input
  readonly: false,

  // 'light' or 'dark'
  theme: 'light',

  // UI language — see i18n section for all supported codes
  language: 'en',

  // Custom translation overrides merged with built-in keys
  translations: null,

  // Toolbar buttons in left-to-right order — '|' adds a visual separator
  toolbar: [
    'viewCode', 'undo', 'redo', 'findReplace', '|',
    'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'removeFormat', '|',
    'heading', 'fontFamily', 'fontSize', '|',
    'foreColor', 'backColor', '|',
    'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
    'indent', 'outdent', '|',
    'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
    'insertDropdown', '|',
    'moreMenu'
  ],

  // Fires on every content change — receives the HTML string and editor instance
  onChange: function(content, editor) {
    console.log('Content updated at', new Date().toISOString());
  },

  // Fires on Ctrl+S or More menu → Save
  onSave: function(content, editor) {
    console.log('Save requested — content length:', content.length);
  },

  // Fires when the editor gains focus
  onFocus: function(editor) {
    console.log('Editor is focused');
  },

  // Fires when the editor loses focus
  onBlur: function(editor) {
    console.log('Editor lost focus');
  },

  // Fires once after the editor finishes initializing
  onReady: function(editor) {
    console.log('Neiki Editor is ready');
  },

  // Show or hide the Help item inside the More menu
  showHelp: true
  
});</pre>
<p>5. The <code>toolbar</code> array accepts any of the following string keys. Add <code>'|'</code> between keys to create visual separator groups.</p>
<ul>
<li><strong>Text formatting:</strong> <code>bold</code>, <code>italic</code>, <code>underline</code>, <code>strikethrough</code>, <code>subscript</code>, <code>superscript</code>, <code>removeFormat</code></li>
<li><strong>Text style:</strong> <code>heading</code>, <code>fontSize</code>, <code>fontFamily</code>, <code>foreColor</code>, <code>backColor</code></li>
<li><strong>Alignment and lists:</strong> <code>alignLeft</code>, <code>alignCenter</code>, <code>alignRight</code>, <code>alignJustify</code>, <code>bulletList</code>, <code>numberedList</code>, <code>indent</code>, <code>outdent</code></li>
<li><strong>Insert Dropdown</strong> (<code>insertDropdown</code>): Renders a single Insert button that opens a dropdown with Link, Image, Table, Emoji, and Special Characters items. Use the individual keys <code>link</code>, <code>image</code>, <code>table</code>, <code>emoji</code>, and <code>specialChars</code> to place these as standalone buttons.</li>
<li><strong>Standalone tools:</strong> <code>undo</code>, <code>redo</code>, <code>findReplace</code>, <code>viewCode</code>, <code>blockquote</code>, <code>horizontalRule</code></li>
<li><strong>More Menu</strong> (<code>moreMenu</code>): Renders a <code>⋯</code> button pinned to the right that opens a dropdown with Save, Preview, Download, Print, Autosave, Clear All, Toggle Theme, Fullscreen, and Help.</li>
</ul>
<p>6. API methods:</p>
<pre>// Get the current editor content as an HTML string
const html = editor.getContent();

// Replace editor content with new HTML
editor.setContent('&lt;p&gt;Replaced content goes here.&lt;/p&gt;');

// Get the editor content as plain text — all HTML tags stripped
const plain = editor.getText();

// Returns true when the editor has no content
const isEmpty = editor.isEmpty();

// Alias for getContent()
const html2 = editor.getHTML();

// Alias for setContent()
editor.setHTML('&lt;p&gt;Set via alias method.&lt;/p&gt;');

// Get a structured JSON representation of the current content
const json = editor.getJSON();

// Populate the editor from a previously captured JSON structure
editor.setJSON(json);

// Move keyboard focus to the editor
editor.focus();

// Remove keyboard focus from the editor
editor.blur();

// Re-enable editing after the editor was disabled
editor.enable();

// Disable all editing input — puts the editor into read-only mode
editor.disable();

// Remove the editor instance and restore the original textarea element
editor.destroy();

// Toggle fullscreen mode on and off
editor.toggleFullscreen();

// Toggle between light and dark themes
editor.toggleTheme();

// Set a specific theme directly
editor.setTheme('dark');

// Manually trigger the onSave callback
editor.triggerSave();

// Open the content preview modal
editor.previewContent();

// Trigger the browser download dialog with content as an HTML file
editor.downloadContent();

// Remove all content from the editor
editor.clearAll();

// Execute a built-in formatting command by name
editor.execCommand('bold');

// Insert an HTML string at the current cursor position
editor.insertHTML('&lt;mark&gt;Highlighted passage&lt;/mark&gt;');

// Wrap the current selection in a specified element with optional attributes
editor.wrapSelection('mark', { class: 'callout' });

// Remove a wrapping element from the current selection
editor.unwrapSelection('mark');

// Return the browser's Selection object for the current selection
const selection = editor.getSelection();</pre>
<p>7. The plugin system adds custom toolbar buttons with full access to the editor instance. Register plugins before or after initialization. They remain available across all instances on the page.</p>
<ul>
<li><strong><code>name</code></strong> (string, required): Unique plugin identifier. No spaces. Used internally to register and retrieve the plugin.</li>
<li><strong><code>icon</code></strong> (string, optional): SVG markup for the toolbar button icon. Omit this property to register a background-only plugin with no button.</li>
<li><strong><code>tooltip</code></strong> (string, optional): Text shown in the button&#8217;s tooltip on hover.</li>
<li><strong><code>action</code></strong> (function, optional): Function called when the toolbar button is clicked. Receives the editor instance as its argument.</li>
<li><strong><code>init</code></strong> (function, optional): Function called once during editor initialization. Receives the editor instance. Use this for one-time setup logic.</li>
</ul>
<pre>// Register a custom reading-time plugin with a toolbar button
NeikiEditor.registerPlugin({

    // Unique identifier for this plugin — no spaces
    name: 'reading-time-counter',

    // SVG icon rendered as the toolbar button graphic
    icon: '&lt;svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"&gt;&lt;circle cx="12" cy="12" r="10"/&gt;&lt;polyline points="12 6 12 12 16 14"/&gt;&lt;/svg&gt;',

    // Tooltip text shown when hovering the toolbar button
    tooltip: 'Estimate Reading Time',

    // Runs when the user clicks the toolbar button
    action: function(editor) {
        const words = editor.getText().trim().split(/\s+/).filter(Boolean).length;
        const minutes = Math.ceil(words / 200);
        alert('Estimated reading time: ' + minutes + ' minute(s)');
    },

    // Runs once when the editor initializes — useful for DOM setup or event listeners
    init: function(editor) {
        console.log('Reading time plugin initialized');
    }
});

// Get an array of all registered plugin objects
const plugins = NeikiEditor.getPlugins();</pre>
<p>8. Themes and Localization:</p>
<pre>// Initialize with dark theme
const editor = new NeikiEditor('#editor', { theme: 'dark' });

// Toggle between light and dark at runtime
editor.toggleTheme();

// Set a specific theme programmatically
editor.setTheme('light');

// Initialize with French UI
const editorFr = new NeikiEditor('#article-body', { language: 'fr' });

// Register custom translation keys for German — only override what you need
// English serves as the fallback for any key you leave out
NeikiEditor.addTranslation('de', {
  'toolbar.bold': 'Fett (Strg+B)',
  'toolbar.italic': 'Kursiv (Strg+I)',
  'toolbar.undo': 'Rückgängig (Strg+Z)',
  'toolbar.redo': 'Wiederholen (Strg+Y)'
});

const editorDe = new NeikiEditor('#article-body', { language: 'de' });

// Alternatively, pass translations inline in the config object
const editorInline = new NeikiEditor('#editor', {
  language: 'de',
  translations: {
    de: {
      'toolbar.bold': 'Fett (Strg+B)',
      'toolbar.italic': 'Kursiv (Strg+I)'
    }
  }
});</pre>
<h2>Framework Integration:</h2>
<p><strong>React:</strong></p>
<pre>import { useEffect, useRef } from 'react';

function ArticleEditor({ initialValue, onChange }) {
    const textareaRef = useRef(null);
    const editorRef = useRef(null);

    useEffect(() =&gt; {
        // Initialize the editor once the component mounts
        editorRef.current = new NeikiEditor(textareaRef.current, {
            onChange: (content) =&gt; onChange?.(content)
        });

        // Destroy the editor instance when the component unmounts
        return () =&gt; editorRef.current?.destroy();
    }, []);

    return &lt;textarea ref={textareaRef} defaultValue={initialValue} /&gt;;
}
</pre>
<p><strong>Vue.js:</strong></p>
<pre>&lt;template&gt;
  &lt;textarea ref="editorEl"&gt;&lt;/textarea&gt;
&lt;/template&gt;

&lt;script&gt;
export default {
    mounted() {
        // Initialize after the component's DOM is ready
        this.editor = new NeikiEditor(this.$refs.editorEl, {
            onChange: (content) =&gt; {
                this.$emit('update:modelValue', content);
            }
        });
    },
    beforeUnmount() {
        // Remove the editor before Vue tears down the component
        this.editor.destroy();
    }
}
&lt;/script&gt;
</pre>
<p><strong>AJAX auto-save:</strong></p>
<pre>// Debounce saves so the server receives one request per 2-second idle window
const editor = new NeikiEditor('#article-body', {
    onChange: debounce(function(content) {
        fetch('/api/posts/draft', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content })
        });
    }, 2000)
});
</pre>
<p><strong>PHP:</strong></p>
<pre>&lt;?php require_once 'php/neiki-editor.php'; ?&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;!-- Output CDN &lt;link&gt; and &lt;script&gt; tags — call once per page --&gt;
    &lt;?= NeikiEditor::assets() ?&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;form method="POST" action="publish.php"&gt;
        &lt;!-- Render the textarea and initialization script for the 'body' field --&gt;
        &lt;?= NeikiEditor::render('body', $post-&gt;content, [
            'minHeight' =&gt; 450,
            'placeholder' =&gt; 'Write your post content here...'
        ]) ?&gt;
        &lt;button type="submit"&gt;Publish&lt;/button&gt;
    &lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<pre>// publish.php — always sanitize HTML before writing to the database
require_once 'php/neiki-editor.php';
$safe = NeikiEditor::sanitize($_POST['body']);
$db-&gt;query('UPDATE posts SET content = ? WHERE id = ?', [$safe, $postId]);
</pre>
<h2>Alternatives:</h2>
<ul>
<li><a href="https://www.cssscript.com/best-wysiwyg-editors/" target="_blank" rel="noopener">10 Best Free JavaScript WYSIWYG Rich Text Editors</a></li>
</ul>
<p>The post <a href="https://www.cssscript.com/wysiwyg-editor-neiki-editor/">Self-Hosted WYSIWYG Editor in Vanilla JS &#8211; Neiki Editor</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/wysiwyg-editor-neiki-editor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">105249</post-id>	</item>
		<item>
		<title>Vanilla Calendar JS Library for Date Picking, Scheduling, and Timelines</title>
		<link>https://www.cssscript.com/calendar-date-pick-schedule-timeline/</link>
					<comments>https://www.cssscript.com/calendar-date-pick-schedule-timeline/#respond</comments>
		
		<dc:creator><![CDATA[CSS Script]]></dc:creator>
		<pubDate>Mon, 20 Apr 2026 03:52:49 +0000</pubDate>
				<category><![CDATA[Date & Time]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[calendar]]></category>
		<category><![CDATA[Date Picker]]></category>
		<category><![CDATA[timeline]]></category>
		<guid isPermaLink="false">https://www.cssscript.com/?p=105243</guid>

					<description><![CDATA[<p><img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="calendar-date-pick-schedule-timeline" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" />A JavaScript calendar library with date picker, schedule grid, timeline view, and helper utilities for vanilla JS, React, and Vue.</p>
<p>The post <a href="https://www.cssscript.com/calendar-date-pick-schedule-timeline/">Vanilla Calendar JS Library for Date Picking, Scheduling, and Timelines</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></description>
										<content:encoded><![CDATA[<img width="150" height="150" src="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?resize=150%2C150&amp;ssl=1" class="attachment-thumbnail size-thumbnail wp-post-image" alt="calendar-date-pick-schedule-timeline" style="float:left; margin:0 15px 15px 0;" decoding="async" loading="lazy" srcset="https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?resize=150%2C150&amp;ssl=1 150w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?zoom=2&amp;resize=150%2C150&amp;ssl=1 300w, https://i0.wp.com/www.cssscript.com/wp-content/uploads/2026/04/calendar-date-pick-schedule-timeline.webp?zoom=3&amp;resize=150%2C150&amp;ssl=1 450w" sizes="auto, (max-width: 150px) 100vw, 150px" /><p>CalendarJS is a feature-rich JavaScript calendar library that allows you to create calendars, date pickers, event schedules, and timelines in modern web apps.</p>
<h2>Features:</h2>
<ul>
<li>Generate modal, inline, auto, and picker style calendars.</li>
<li>Pick single dates, date ranges, and date &amp; time values.</li>
<li>Restrict selectable dates with valid date windows.</li>
<li>Change weekday order for local business calendars.</li>
<li>Show event markers inside the calendar grid.</li>
<li>Build day, week, and weekday schedule views.</li>
<li>Drag and resize schedule items in the grid.</li>
<li>Track schedule history with undo and redo actions.</li>
<li>Block editing in protected time ranges.</li>
<li>Switch timeline items across left, right, top, and bottom layouts.</li>
<li>Filter timeline output by month in monthly mode.</li>
<li>Convert ISO dates to Excel-style serial numbers.</li>
<li>Format dates into relative time labels.</li>
</ul>
<h2>Use cases:</h2>
<ul>
<li>Build a hotel booking form with date range and time selection.</li>
<li>Add a weekly staff planner to an internal operations dashboard.</li>
<li>Show a release history view inside a product changelog page.</li>
<li>Convert spreadsheet date values in import and export flows.</li>
</ul>
<h2>How to use it:</h2>
<p>1. Install the package with NPM and import modules into your project:</p>
<pre>npm install @calendarjs/ce</pre>
<pre>import { Calendar } from '@calendarjs/ce';
import { Schedule } from '@calendarjs/ce';
import { Timeline } from '@calendarjs/ce';</pre>
<p>2. Or load it from a CDN:</p>
<pre>&lt;!-- Load the stylesheet first --&gt;
&lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/style.min.css" /&gt;

&lt;!-- Load LemonadeJS before CalendarJS in browser builds --&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"&gt;&lt;/script&gt;
</pre>
<p>3. Create an inline range calendar and a time picker</p>
<pre>&lt;div id="report-range"&gt;&lt;/div&gt;
&lt;input id="publish-at" /&gt;

&lt;script&gt;
  // Create an inline range calendar for a reporting window
  const reportRange = calendarjs.Calendar(document.getElementById('report-range'), {
    type: 'inline',
    range: true,
    grid: true,
    startingDay: 1,
    validRange: ['2026-04-01', '2026-05-31'],
    value: ['2026-04-12', '2026-04-19'],
    onchange: function(self, value) {
      // Read the selected range after every change
      console.log('Range value:', value);
    }
  });

  // Bind a popup calendar to an input field
  const publishAt = calendarjs.Calendar(document.getElementById('publish-at'), {
    type: 'default',
    input: document.getElementById('publish-at'),
    time: true,
    format: 'MMMM DD, YYYY HH:MI',
    value: '2026-04-20 15:45:00',
    onchange: function(self, value) {
      // Store the picked date and time in your form state
      console.log('Publish at:', value);
    }
  });

  // Open the popup calendar from code if you need a custom trigger
  publishAt.open();
&lt;/script&gt;
</pre>
<p>4. Build a weekly schedule:</p>
<pre>&lt;div id="team-board"&gt;&lt;/div&gt;

&lt;script&gt;
  // Seed the scheduler with a few meetings
  const teamBoard = calendarjs.Schedule(document.getElementById('team-board'), {
    type: 'week',
    value: '2026-04-20',
    grid: 30,
    overlap: false,
    validRange: ['08:00', '19:00'],
    data: [
      {
        guid: 'evt-101',
        title: 'Design review',
        date: '2026-04-21',
        start: '09:00',
        end: '10:30',
        color: '#b86e2f'
      },
      {
        guid: 'evt-102',
        title: 'Ops sync',
        date: '2026-04-22',
        start: '13:00',
        end: '14:00',
        color: '#5e7f4a'
      }
    ],
    oncreate: function(self, events) {
      // Persist new events after user edits
      console.log('Created:', events);
    },
    onchangeevent: function(self, newValue, oldValue) {
      // Track updates for autosave or audit logs
      console.log('Changed:', newValue, oldValue);
    },
    onerror: function(self, message) {
      // Show validation feedback in your UI
      console.log('Schedule error:', message);
    }
  });

  // Lock early morning and late evening blocks
  teamBoard.setReadOnly([['00:00', '08:00'], ['19:00', '23:59']]);

  // Add an event from code
  teamBoard.addEvents({
    guid: 'evt-103',
    title: 'Client handoff',
    date: '2026-04-23',
    start: '16:00',
    end: '17:00',
    color: '#9f5140'
  });
&lt;/script&gt;
</pre>
<p>5. Generate a timeline:</p>
<pre>&lt;div id="release-timeline"&gt;&lt;/div&gt;

&lt;script&gt;
  // Create a project timeline with dated milestones
  const releaseTimeline = calendarjs.Timeline(document.getElementById('release-timeline'), {
    type: 'monthly',
    align: 'left',
    order: 'asc',
    controls: true,
    data: [
      {
        title: 'Spec freeze',
        subtitle: 'Planning',
        description: 'The team locked scope for the next release cycle.',
        date: new Date('2026-04-02'),
        borderColor: '#b86e2f',
        borderStyle: 'solid'
      },
      {
        title: 'QA pass',
        subtitle: 'Validation',
        description: 'QA finished the final regression pass.',
        date: new Date('2026-04-11'),
        borderColor: '#5e7f4a',
        borderStyle: 'solid'
      },
      {
        title: 'Public launch',
        subtitle: 'Release',
        description: 'The new build went live to all users.',
        date: new Date('2026-04-18'),
        borderColor: '#9f5140',
        borderStyle: 'dashed'
      }
    ],
    onupdate: function(self) {
      // React to monthly filtering or data changes
      console.log('Timeline updated');
    }
  });

  // Move to the next month in monthly mode
  releaseTimeline.next();
  releaseTimeline.updateResult();
&lt;/script&gt;
</pre>
<p>6. Use the helper utilities:</p>
<pre>// Read the current UTC date string
const nowValue = calendarjs.Helpers.now();

// Convert an ISO date to an Excel style serial number
const serialValue = calendarjs.Helpers.dateToNum('2026-04-20 10:00:00');

// Convert the serial number back to a date string
const restoredDate = calendarjs.Helpers.numToDate(serialValue);

// Turn a timestamp into a relative label
const relativeLabel = calendarjs.Helpers.prettify('2026-04-19 10:00:00');

console.log(nowValue, serialValue, restoredDate, relativeLabel);
</pre>
<h2>Configuration options:</h2>
<h3>Calendar options</h3>
<ul>
<li><strong><code>type</code></strong> (<code>'default' | 'auto' | 'picker' | 'inline'</code>): Sets the calendar display mode.</li>
<li><strong><code>format</code></strong> (<code>string</code>): Sets the output format for rendered values.</li>
<li><strong><code>range</code></strong> (<code>boolean</code>): Turns range selection on or off.</li>
<li><strong><code>value</code></strong> (<code>number | string</code>): Sets the initial calendar value.</li>
<li><strong><code>numeric</code></strong> (<code>boolean</code>): Returns Excel style serial numbers in place of ISO strings.</li>
<li><strong><code>footer</code></strong> (<code>boolean</code>): Shows or hides the footer controls.</li>
<li><strong><code>time</code></strong> (<code>boolean</code>): Shows the hour and minute picker.</li>
<li><strong><code>grid</code></strong> (<code>boolean</code>): Turns grid mode on or off.</li>
<li><strong><code>placeholder</code></strong> (<code>string</code>): Sets placeholder text for bound inputs.</li>
<li><strong><code>disabled</code></strong> (<code>boolean</code>): Locks the calendar UI.</li>
<li><strong><code>startingDay</code></strong> (<code>number</code>): Sets the first weekday from <code>0</code> to <code>6</code>.</li>
<li><strong><code>validRange</code></strong> (<code>number[] | string[]</code>): Restricts selectable dates to a range.</li>
<li><strong><code>data</code></strong> (<code>Array&lt;{date: string, [key: string]: any}&gt;</code>): Adds event data for calendar markers.</li>
<li><strong><code>wheel</code></strong> (<code>boolean</code>): Turns mouse wheel view changes on or off.</li>
<li><strong><code>input</code></strong> (<code>HTMLInputElement | 'auto'</code>): Binds the calendar to an input element.</li>
<li><strong><code>initInput</code></strong> (<code>boolean</code>): Applies input setup and calendar classes.</li>
<li><strong><code>onchange</code></strong> (<code>(self: object, value: string) =&gt; void</code>): Runs after the value changes.</li>
<li><strong><code>onupdate</code></strong> (<code>(self: object, value: string) =&gt; void</code>): Runs after the view updates.</li>
<li><strong><code>onclose</code></strong> (<code>(self: object, origin: string) =&gt; void</code>): Runs after the modal closes.</li>
<li><strong><code>onopen</code></strong> (<code>(self: object) =&gt; void</code>): Runs after the modal opens.</li>
<li><strong><code>onChange</code></strong> (<code>(e: Event) =&gt; void</code>): Exposes a React focused change hook.</li>
</ul>
<h3>Schedule options</h3>
<ul>
<li><strong><code>type</code></strong> (<code>'week' | 'day' | 'weekdays'</code>): Sets the schedule view type.</li>
<li><strong><code>weekly</code></strong> (<code>boolean</code>): Uses weekday columns in place of dated columns.</li>
<li><strong><code>value</code></strong> (<code>string</code>): Sets the initial ISO date in <code>YYYY-MM-DD</code> format.</li>
<li><strong><code>data</code></strong> (<code>Schedule.Event[]</code>): Loads the schedule with event records.</li>
<li><strong><code>grid</code></strong> (<code>number</code>): Sets the grid step in minutes.</li>
<li><strong><code>overlap</code></strong> (<code>boolean</code>): Permits overlapping events.</li>
<li><strong><code>validRange</code></strong> (<code>string[]</code>): Restricts visible editing hours.</li>
<li><strong><code>readOnlyRange</code></strong> (<code>string[] | string[][]</code>): Marks one or more time ranges as read only.</li>
<li><strong><code>onbeforechange</code></strong> (<code>(self: Instance, state: object) =&gt; boolean | void</code>): Runs before a grid drag or resize change.</li>
<li><strong><code>onchange</code></strong> (<code>(self: Instance, state: object) =&gt; void</code>): Runs after a grid state change.</li>
<li><strong><code>onbeforecreate</code></strong> (<code>(self: Instance, events: Event | Event[], e?: MouseEvent) =&gt; boolean | void</code>): Runs before event creation.</li>
<li><strong><code>oncreate</code></strong> (<code>(self: Instance, events: Event | Event[], e?: MouseEvent) =&gt; void</code>): Runs after event creation.</li>
<li><strong><code>ondblclick</code></strong> (<code>(self: Instance, event: Event) =&gt; void</code>): Runs on event double click.</li>
<li><strong><code>onedition</code></strong> (<code>(self: Instance, event: Event) =&gt; void</code>): Runs when editing starts.</li>
<li><strong><code>ondelete</code></strong> (<code>(self: Instance, event: Event) =&gt; void</code>): Runs after event deletion.</li>
<li><strong><code>onchangeevent</code></strong> (<code>(self: Instance, newValue: Partial&lt;Event&gt;, oldValue: Partial&lt;Event&gt;) =&gt; void</code>): Runs after event data changes.</li>
<li><strong><code>onerror</code></strong> (<code>(self: Instance, message: string) =&gt; void</code>): Runs after validation errors.</li>
</ul>
<h3>Schedule event object</h3>
<ul>
<li><strong><code>type</code></strong> (<code>string | null</code>): Sets the event type label.</li>
<li><strong><code>title</code></strong> (<code>string</code>): Sets the event title.</li>
<li><strong><code>start</code></strong> (<code>string</code>): Sets the start time in <code>HH:MM</code>.</li>
<li><strong><code>end</code></strong> (<code>string</code>): Sets the end time in <code>HH:MM</code>.</li>
<li><strong><code>guests</code></strong> (<code>string</code>): Stores guest data.</li>
<li><strong><code>location</code></strong> (<code>string</code>): Stores a location label.</li>
<li><strong><code>description</code></strong> (<code>string</code>): Stores descriptive text.</li>
<li><strong><code>color</code></strong> (<code>string</code>): Sets the event color.</li>
<li><strong><code>readonly</code></strong> (<code>boolean</code>): Locks the event from editing.</li>
<li><strong><code>guid</code></strong> (<code>string</code>): Stores the unique event id.</li>
<li><strong><code>date</code></strong> (<code>string</code>): Stores the ISO date when <code>weekly</code> is <code>false</code>.</li>
<li><strong><code>weekday</code></strong> (<code>number</code>): Stores the weekday from <code>0</code> to <code>6</code> when <code>weekly</code> is <code>true</code>.</li>
</ul>
<h3>Timeline options</h3>
<ul>
<li><strong><code>data</code></strong> (<code>Timeline.Item[]</code>): Loads the timeline with item records.</li>
<li><strong><code>type</code></strong> (<code>"monthly" | string</code>): Sets the timeline mode.</li>
<li><strong><code>align</code></strong> (<code>"left" | "right" | "top" | "bottom" | string</code>): Sets item alignment.</li>
<li><strong><code>message</code></strong> (<code>string</code>): Sets the empty state message.</li>
<li><strong><code>order</code></strong> (<code>'asc' | 'desc' | undefined</code>): Sets chronological order.</li>
<li><strong><code>width</code></strong> (<code>number</code>): Sets container width.</li>
<li><strong><code>height</code></strong> (<code>number</code>): Sets container height.</li>
<li><strong><code>url</code></strong> (<code>string</code>): Sets a remote data URL.</li>
<li><strong><code>remote</code></strong> (<code>boolean</code>): Turns remote loading on.</li>
<li><strong><code>onupdate</code></strong> (<code>(self: Object) =&gt; void</code>): Runs after the visible timeline data updates.</li>
</ul>
<h3>Timeline item object</h3>
<ul>
<li><strong><code>title</code></strong> (<code>string</code>): Sets the main item label.</li>
<li><strong><code>subtitle</code></strong> (<code>string</code>): Sets the secondary label.</li>
<li><strong><code>description</code></strong> (<code>string</code>): Sets the item body text.</li>
<li><strong><code>date</code></strong> (<code>string | Date</code>): Sets the item date.</li>
<li><strong><code>borderColor</code></strong> (<code>string</code>): Sets the item border color.</li>
<li><strong><code>borderStyle</code></strong> (<code>string</code>): Sets the item border style.</li>
</ul>
<h2>API methods:</h2>
<pre>// Create a calendar instance
const calendar = calendarjs.Calendar(document.getElementById('calendar-root'), {
  type: 'inline'
});

// Open the calendar modal
calendar.open();

// Close the calendar modal
calendar.close({ origin: 'button' });

// Check modal state
calendar.isClosed();

// Switch the calendar view
calendar.setView('months');

// Move forward in the current view
calendar.next();

// Move backward in the current view
calendar.prev();

// Reset the selected value
calendar.reset();

// Read the current value
calendar.getValue();

// Write a new value
calendar.setValue('2026-04-24');

// Accept the current selection
calendar.update();


// Create a schedule instance
const schedule = calendarjs.Schedule(document.getElementById('schedule-root'), {
  type: 'week'
});

// Add one event
schedule.addEvents({
  guid: 'evt-201',
  title: 'Roadmap sync',
  date: '2026-04-24',
  start: '10:00',
  end: '11:00',
  color: '#b86e2f'
});

// Add multiple events
schedule.addEvents([
  {
    guid: 'evt-202',
    title: 'Editorial review',
    date: '2026-04-24',
    start: '12:00',
    end: '13:00',
    color: '#5e7f4a'
  },
  {
    guid: 'evt-203',
    title: 'Launch prep',
    date: '2026-04-25',
    start: '15:00',
    end: '16:00',
    color: '#9f5140'
  }
]);

// Update an event by guid
schedule.updateEvent('evt-201', {
  title: 'Roadmap sync v2',
  start: '10:30'
});

// Update an event by object reference
const firstRecord = schedule.getData()[0];
schedule.updateEvent(firstRecord, {
  end: '11:30'
});

// Delete by guid
schedule.deleteEvents('evt-202');

// Delete by object
schedule.deleteEvents(firstRecord);

// Delete multiple records
schedule.deleteEvents(['evt-201', 'evt-203']);

// Clear the current visual selection
schedule.resetSelection();

// Restrict editable hours
schedule.setRange(['09:00', '18:00']);

// Mark time ranges as read only
schedule.setReadOnly([['00:00', '09:00'], ['18:00', '23:59']]);

// Read all schedule data
schedule.getData();

// Replace all schedule data
schedule.setData([
  {
    guid: 'evt-301',
    title: 'Support block',
    date: '2026-04-26',
    start: '11:00',
    end: '12:00',
    color: '#b86e2f'
  }
]);

// Read the DOM node for an event
schedule.getEvent('evt-301');

// Render the schedule again
schedule.render();

// Undo the last change
schedule.undo();

// Redo the last undone change
schedule.redo();

// Move to the next period
schedule.next();

// Move to the previous period
schedule.prev();


// Create a timeline instance
const timeline = calendarjs.Timeline(document.getElementById('timeline-root'), {
  type: 'monthly',
  data: []
});

// Read the bound timeline data
timeline.data;

// Move to the next month in monthly mode
timeline.next();

// Move to the previous month in monthly mode
timeline.prev();

// Rebuild the visible item list
timeline.updateResult();

// Fetch remote data when remote mode is active
timeline.fetchRemote();


// Convert a value to two digits
calendarjs.Helpers.two(7);

// Validate a Date object
calendarjs.Helpers.isValidDate(new Date());

// Validate an ISO date string
calendarjs.Helpers.isValidDateFormat('2026-04-24');

// Convert a Date to a string
calendarjs.Helpers.toString(new Date(), false);

// Convert a string into an array
calendarjs.Helpers.toArray('2026-04-24 12:30:00');

// Convert an array into a string date
calendarjs.Helpers.arrayToStringDate([2026, 4, 24, 12, 30, 0]);

// Convert a Date or string to an Excel style serial
calendarjs.Helpers.dateToNum('2026-04-24 12:30:00');

// Convert an Excel style serial back to a string
calendarjs.Helpers.numToDate(46136.520833333336);

// Convert an Excel style serial to an array
calendarjs.Helpers.numToDate(46136.520833333336, true);

// Convert a timestamp into a relative label
calendarjs.Helpers.prettify('2026-04-23 12:30:00');

// Update every .prettydate node on the page
calendarjs.Helpers.prettifyAll();

// Read the current date string
calendarjs.Helpers.now();

// Read localized weekday names
calendarjs.Helpers.weekdays;

// Read localized short weekday names
calendarjs.Helpers.weekdaysShort;

// Read localized month names
calendarjs.Helpers.months;

// Read localized short month names
calendarjs.Helpers.monthsShort;


// Set custom labels
calendarjs.setDictionary({
  Reset: 'Clear',
  Done: 'Apply'
});

// Read package metadata
calendarjs.about();
</pre>
<h2>Events:</h2>
<pre>// Run after a calendar value changes
calendarjs.Calendar(document.getElementById('calendar-a'), {
  onchange: function(self, value) {
    console.log('Calendar change:', value);
  }
});

// Run after the calendar view updates
calendarjs.Calendar(document.getElementById('calendar-b'), {
  onupdate: function(self, value) {
    console.log('Calendar update:', value);
  }
});

// Run after the calendar modal closes
calendarjs.Calendar(document.getElementById('calendar-c'), {
  onclose: function(self, origin) {
    console.log('Calendar closed from:', origin);
  }
});

// Run after the calendar modal opens
calendarjs.Calendar(document.getElementById('calendar-d'), {
  onopen: function(self) {
    console.log('Calendar opened');
  }
});

// React focused change callback
calendarjs.Calendar(document.getElementById('calendar-e'), {
  onChange: function(e) {
    console.log('React style change event:', e);
  }
});


// Run before a schedule drag or resize change
calendarjs.Schedule(document.getElementById('schedule-a'), {
  onbeforechange: function(self, state) {
    console.log('Before grid change:', state);
    return true;
  }
});

// Run after a schedule grid change
calendarjs.Schedule(document.getElementById('schedule-b'), {
  onchange: function(self, state) {
    console.log('Grid changed:', state);
  }
});

// Run before creating events
calendarjs.Schedule(document.getElementById('schedule-c'), {
  onbeforecreate: function(self, events, e) {
    console.log('Before create:', events, e);
    return true;
  }
});

// Run after creating events
calendarjs.Schedule(document.getElementById('schedule-d'), {
  oncreate: function(self, events, e) {
    console.log('Created:', events, e);
  }
});

// Run on event double click
calendarjs.Schedule(document.getElementById('schedule-e'), {
  ondblclick: function(self, event) {
    console.log('Double click:', event);
  }
});

// Run when event editing starts
calendarjs.Schedule(document.getElementById('schedule-f'), {
  onedition: function(self, event) {
    console.log('Editing:', event);
  }
});

// Run after deleting an event
calendarjs.Schedule(document.getElementById('schedule-g'), {
  ondelete: function(self, event) {
    console.log('Deleted:', event);
  }
});

// Run after event data changes
calendarjs.Schedule(document.getElementById('schedule-h'), {
  onchangeevent: function(self, newValue, oldValue) {
    console.log('Event changed:', newValue, oldValue);
  }
});

// Run after schedule validation errors
calendarjs.Schedule(document.getElementById('schedule-i'), {
  onerror: function(self, message) {
    console.log('Schedule error:', message);
  }
});


// Run after timeline data updates
calendarjs.Timeline(document.getElementById('timeline-a'), {
  onupdate: function(self) {
    console.log('Timeline updated:', self);
  }
});
</pre>
<h2>Alternatives:</h2>
<ul>
<li><strong><a href="https://www.jqueryscript.net/time-clock/Full-Size-Drag-Drop-Calendar-Plugin-FullCalendar.html" target="_blank" rel="noopener">FullCalendar</a></strong>: Handles large calendar apps with month, week, resource, and drag editing views.</li>
<li><strong><a href="https://www.cssscript.com/full-featured-calendar-javascript-library-tui/">TOAST UI Calendar</a></strong>: Ships a full calendar UI with multiple calendar layers and template hooks.</li>
</ul>
<p>The post <a href="https://www.cssscript.com/calendar-date-pick-schedule-timeline/">Vanilla Calendar JS Library for Date Picking, Scheduling, and Timelines</a> appeared first on <a href="https://www.cssscript.com">CSS Script</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cssscript.com/calendar-date-pick-schedule-timeline/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">105243</post-id>	</item>
	</channel>
</rss>
