From 27bcd3554d64772425ea0d9369bef860fc82e552 Mon Sep 17 00:00:00 2001 From: chaxus Date: Mon, 29 Jan 2024 00:27:28 +0800 Subject: [PATCH] feat: add popover component --- .vscode/settings.json | 1 + packages/ranui/components/checkbox/index.ts | 14 +- .../ranui/components/colorpicker/index.less | 33 ++ .../ranui/components/colorpicker/index.ts | 87 +++++ packages/ranui/components/content/index.less | 3 + packages/ranui/components/content/index.ts | 50 +++ packages/ranui/components/popover/index.less | 78 ++++ packages/ranui/components/popover/index.ts | 170 ++++++++ packages/ranui/components/select/index.ts | 4 +- packages/ranui/index.html | 365 +++++++++--------- packages/ranui/index.ts | 3 + packages/ranui/package.json | 15 + packages/ranui/utils/color.ts | 167 ++++++++ packages/ranui/vite.config.ts | 4 + 14 files changed, 809 insertions(+), 185 deletions(-) create mode 100644 packages/ranui/components/colorpicker/index.less create mode 100644 packages/ranui/components/colorpicker/index.ts create mode 100644 packages/ranui/components/content/index.less create mode 100644 packages/ranui/components/content/index.ts create mode 100644 packages/ranui/components/popover/index.less create mode 100644 packages/ranui/components/popover/index.ts create mode 100644 packages/ranui/utils/color.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index ce599397f..df92efb8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "brotli", "chaxus", "classname", + "colorpicker", "componentization", "Crossentropy", "dataavailable", diff --git a/packages/ranui/components/checkbox/index.ts b/packages/ranui/components/checkbox/index.ts index 0bf5b5bdd..3c5037984 100644 --- a/packages/ranui/components/checkbox/index.ts +++ b/packages/ranui/components/checkbox/index.ts @@ -12,22 +12,25 @@ export class Checkbox extends (HTMLElementSSR()!) { checkInner: HTMLSpanElement; context: Context; static get observedAttributes(): string[] { - return ['disabled', 'icon', 'effect', 'iconSize', 'sheet']; + return ['disabled', 'checked']; } constructor() { super(); - this.setAttribute('class', 'ran-checkbox') this.checkInput = document.createElement('input') this.checkInput.setAttribute('class', 'ran-checkbox-input') this.checkInput.setAttribute('type', 'checkbox') this.checkInner = document.createElement('span') this.checkInner.setAttribute('class', 'ran-checkbox-inner') - this.appendChild(this.checkInput) - this.appendChild(this.checkInner) this.context = { checked: false } } + get disabled(): string { + return this.getAttribute('disabled') || '' + } + set disabled(value: string) { + this.setAttribute('disabled', value); + } get checked(): boolean { return this.context.checked } @@ -60,6 +63,9 @@ export class Checkbox extends (HTMLElementSSR()!) { this.update() } connectedCallback(): void { + this.setAttribute('class', 'ran-checkbox') + this.appendChild(this.checkInput) + this.appendChild(this.checkInner) this.addEventListener('click', this.onChange) } disconnectCallback(): void { diff --git a/packages/ranui/components/colorpicker/index.less b/packages/ranui/components/colorpicker/index.less new file mode 100644 index 000000000..e78be0d81 --- /dev/null +++ b/packages/ranui/components/colorpicker/index.less @@ -0,0 +1,33 @@ +.ran-colorpicker { + box-sizing: border-box; + min-width: 32px; + height: 32px; + border-radius: 6px; + border: 1px solid #d9d9d9; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + background: #ffffff; + padding: 3px; + &-block { + position: relative; + border-radius: 4px; + width: 24px; + height: 24px; + box-shadow: inset 0 0 1px 0 rgba(0, 0, 0, 0.25); + background-image: conic-gradient( + rgba(0, 0, 0, 0.06) 0 25%, + transparent 0 50%, + rgba(0, 0, 0, 0.06) 0 75%, + transparent 0 + ); + background-size: 50% 50%; + } + &-inner { + width: 100%; + height: 100%; + border-radius: inherit; + } +} diff --git a/packages/ranui/components/colorpicker/index.ts b/packages/ranui/components/colorpicker/index.ts new file mode 100644 index 000000000..bff627b30 --- /dev/null +++ b/packages/ranui/components/colorpicker/index.ts @@ -0,0 +1,87 @@ +import { addClassToElement, removeClassToElement } from 'ranuts'; +import { HTMLElementSSR, createCustomError } from '@/utils/index'; +import '@/components/popover' +import '@/components/content' +import './index.less' + +// RGBA: red、green、blue, 透明度 +// HSL: 色相、饱和度、亮度,透明度 +// HSB: 色相、饱和度、明度,透明度 +// HSV: 色相、饱和度、明度,透明度 + +interface Context { + disabled: boolean; + value: string +} + +export class ColorPicker extends (HTMLElementSSR()!) { + colorpicker: HTMLDivElement; + colorpickerInner: HTMLDivElement; + context: Context; + popoverBlock: HTMLElement; + popoverContent: HTMLElement; + static get observedAttributes(): string[] { + return ['disabled', 'value']; + } + constructor() { + super(); + this.setAttribute('class', 'ran-colorpicker') + this.popoverBlock = document.createElement('r-popover') + this.popoverBlock.setAttribute('class', 'ran-popover') + this.popoverContent = document.createElement('r-content') + this.popoverContent.setAttribute('class', 'ran-content') + this.colorpicker = document.createElement('div') + this.colorpicker.setAttribute('class', 'ran-colorpicker-block') + this.colorpickerInner = document.createElement('div') + this.colorpickerInner.setAttribute('class', 'ran-colorpicker-inner') + this.popoverBlock.appendChild(this.colorpicker) + this.popoverBlock.appendChild(this.popoverContent) + this.colorpicker.appendChild(this.colorpickerInner) + this.appendChild(this.popoverBlock) + this.context = { + value: '', + disabled: false + } + } + get value(): string { + return this.context.value + } + set value(value: string) { + this.setAttribute('value', value); + this.updateColorValue(value) + } + updateColorValue = (value: string): void => { + if (value !== this.context.value) { + this.colorpickerInner.style.setProperty('background', value) + this.context.value = value + } + } + openColorPicker = (): void => { + this.popoverContent.innerHTML = '1111' + } + connectedCallback(): void { + this.addEventListener('click', this.openColorPicker) + } + disconnectCallback(): void { + this.removeEventListener('click', this.openColorPicker) + } + attributeChangedCallback(n: string, o: string, v: string): void { + if (o !== v) { + if (n === 'value') { + this.updateColorValue(v) + } + } + + } +} + +function Custom() { + if (typeof document !== 'undefined' && !customElements.get('r-colorpicker')) { + customElements.define('r-colorpicker', ColorPicker); + return ColorPicker; + } else { + return createCustomError('document is undefined or r-colorpicker is exist'); + } +} + +export default Custom(); \ No newline at end of file diff --git a/packages/ranui/components/content/index.less b/packages/ranui/components/content/index.less new file mode 100644 index 000000000..0f9872e2d --- /dev/null +++ b/packages/ranui/components/content/index.less @@ -0,0 +1,3 @@ +.ran-content { + display: none; +} \ No newline at end of file diff --git a/packages/ranui/components/content/index.ts b/packages/ranui/components/content/index.ts new file mode 100644 index 000000000..747d545fb --- /dev/null +++ b/packages/ranui/components/content/index.ts @@ -0,0 +1,50 @@ +import { HTMLElementSSR, createCustomError } from '@/utils/index'; +import './index.less' + + +export class Content extends (HTMLElementSSR()!) { + observer: MutationObserver; + constructor() { + super(); + this.observer = new MutationObserver(this.callback); + } + callback = (mutations: MutationRecord[], observer: MutationObserver): void => { + for (const mutation of mutations) { + if (mutation.type === "childList") { + // A child node has been added or removed. + this.onChange(mutation) + } else if (mutation.type === "attributes") { + // "The " + mutation.attributeName + " attribute was modified." + this.onChange(mutation) + } + } + }; + onChange = (mutation: MutationRecord): void => { + this.dispatchEvent( + new CustomEvent('change', { + detail: { + type: mutation.type, + value: { content: this.innerHTML, mutation } + }, + }), + ); + } + connectedCallback(): void { + this.setAttribute('class', 'ran-content') + this.observer.observe(this, { attributes: true, childList: true, subtree: true }); + } + disconnectCallback(): void { + this.observer.disconnect() + } +} + +function Custom() { + if (typeof document !== 'undefined' && !customElements.get('r-content')) { + customElements.define('r-content', Content); + return Content; + } else { + return createCustomError('document is undefined or r-content is exist'); + } +} + +export default Custom(); \ No newline at end of file diff --git a/packages/ranui/components/popover/index.less b/packages/ranui/components/popover/index.less new file mode 100644 index 000000000..3ef59c0ae --- /dev/null +++ b/packages/ranui/components/popover/index.less @@ -0,0 +1,78 @@ +.ran-popover { + box-sizing: border-box; + position: relative; + display: inline-block; + margin: 0; + padding: 0; + &-content { + box-sizing: border-box; + margin: 0; + padding: 0; + color: rgba(0, 0, 0, 0.88); + font-size: 14px; + list-style: none; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + position: absolute; + z-index: 1030; + font-weight: normal; + white-space: normal; + text-align: start; + cursor: auto; + user-select: text; + transform-origin: var(--ran-x, 50%) var(--ran-y, 50%); + background: #fff; + transition: opacity 0.2s; + &-inner { + background-color: #fff; + background-clip: padding-box; + border-radius: 8px; + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); + padding: 12px; + } + &-arrow { + transform: translateX(-50%) translateY(100%) rotate(180deg); + z-index: 1; + display: block; + pointer-events: none; + width: 16px; + height: 16px; + overflow: hidden; + position: absolute; + &::before { + position: absolute; + bottom: 0; + inset-inline-start: 0; + width: 16px; + height: 8px; + background: #fff; + clip-path: polygon( + 1.6568542494923806px 100%, + 50% 1.6568542494923806px, + 14.34314575050762px 100%, + 1.6568542494923806px 100% + ); + clip-path: path( + "M 0 8 A 4 4 0 0 0 2.82842712474619 6.82842712474619 L 6.585786437626905 3.0710678118654755 A 2 2 0 0 1 9.414213562373096 3.0710678118654755 L 13.17157287525381 6.82842712474619 A 4 4 0 0 0 16 8 Z" + ); + content: ""; + } + &::after { + content: ""; + position: absolute; + width: 8.970562748477143px; + height: 8.970562748477143px; + bottom: 0; + inset-inline: 0; + margin: auto; + border-radius: 0 0 2px 0; + transform: translateY(50%) rotate(-135deg); + box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.05); + z-index: 0; + background: transparent; + } + } + } +} diff --git a/packages/ranui/components/popover/index.ts b/packages/ranui/components/popover/index.ts new file mode 100644 index 000000000..8b77be390 --- /dev/null +++ b/packages/ranui/components/popover/index.ts @@ -0,0 +1,170 @@ +import { addClassToElement, removeClassToElement } from 'ranuts'; +import { HTMLElementSSR, createCustomError } from '@/utils/index'; +import './index.less' + +// index.ts:29 Uncaught DOMException: Failed to construct 'CustomElement': The result must not have children +// index.ts:31 Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes + +export class Popover extends (HTMLElementSSR()!) { + _slot: HTMLSlotElement; + popoverBlock: HTMLDivElement; + popoverContent?: HTMLDivElement; + popoverArrow?: HTMLDivElement; + popoverInner?: HTMLDivElement; + popoverInnerBlock?: HTMLDivElement; + removePopoverTimeId?: NodeJS.Timeout; + static get observedAttributes(): string[] { + return ['placement', 'arrow', 'trigger']; + } + constructor() { + super(); + this._slot = document.createElement('slot'); + this.popoverBlock = document.createElement('div') + this.popoverBlock.setAttribute('class', 'ran-popover-block') + this.popoverBlock.appendChild(this._slot) + } + get placement(): string { + return this.getAttribute('placement') || 'top' + } + set placement(value: string) { + this.setAttribute('placement', value); + } + get arrow(): string { + return this.getAttribute('arrow') || '' + } + set arrow(value: string) { + this.setAttribute('arrow', value); + } + get trigger(): string { + return this.getAttribute('trigger') || '' + } + set trigger(value: string) { + this.setAttribute('trigger', value); + } + get getPopupContainerId(): string { + return this.getAttribute('getPopupContainerId') || '' + } + set getPopupContainerId(value: string) { + this.setAttribute('getPopupContainerId', value); + } + createContent = (content: string): void => { + if (!content) return + if (!this.popoverContent) { + const div = document.createElement('div') + this.popoverContent = document.createElement('div') + this.popoverContent.setAttribute('class', 'ran-popover-content') + this.popoverArrow = document.createElement('div') + this.popoverArrow.setAttribute('class', 'ran-popover-content-arrow') + this.popoverInner = document.createElement('div') + this.popoverInner.setAttribute('class', 'ran-popover-content-inner') + this.popoverInnerBlock = document.createElement('div') + this.popoverInnerBlock.setAttribute('class', 'ran-popover-content-inner-block') + this.popoverContent.appendChild(this.popoverArrow) + this.popoverContent.appendChild(this.popoverInner) + this.popoverInner.appendChild(this.popoverInnerBlock) + div.appendChild(this.popoverContent) + document.body.appendChild(div) + } + if (this.popoverInnerBlock) { + this.popoverInnerBlock.innerHTML = content + } + } + watchContent = (e: Event): void => { + const { type, value } = (e).detail + if (type === "childList") { + this.createContent(value.content) + } + } + placementPosition = (): void => { + if (!this.popoverInnerBlock || !this.popoverContent) return; + this.popoverContent?.style.setProperty('display', 'block') + this.popoverContent?.style.setProperty('opacity', '1') + const rect = this.getBoundingClientRect(); + const { top, left, bottom, width } = rect; + this.popoverContent.style.setProperty('--ran-x', `${(left + window.scrollX).toFixed(2)}px`); + this.popoverContent.style.setProperty('--ran-y', `${(top + window.scrollY).toFixed(2)}px`); + let popoverTop = bottom + window.scrollY; + let popoverLeft = left + window.scrollX; + const root = document.getElementById(this.getPopupContainerId); + const arrowHeight = 8 + let popoverArrowTransform = `translateX(-50%) translateY(-40%) rotate(0deg)` + let popoverArrowTop = -arrowHeight + let popoverArrowLeft = left + this.popoverContent.clientWidth / 2 + if (this.placement === 'top') { + popoverTop = top + window.scrollY - this.popoverContent.clientHeight - arrowHeight; + if (this.getPopupContainerId && root) { + popoverTop = top - root.getBoundingClientRect().top - this.popoverContent.clientHeight; + popoverLeft = left - root.getBoundingClientRect().left; + } + popoverArrowTransform = 'translateX(-50%) translateY(40%) rotate(180deg)' + popoverArrowTop = this.popoverContent.clientHeight - arrowHeight; + popoverArrowLeft = width / 2 + } + this.popoverArrow?.style.setProperty('inset', `${popoverArrowTop}px auto auto ${popoverArrowLeft}px`) + this.popoverArrow?.style.setProperty('transform', popoverArrowTransform) + this.popoverContent.style.setProperty('inset', `${popoverTop}px auto auto ${popoverLeft}px`); + }; + hoverPopover = (): void => { + if (this.removePopoverTimeId) { + clearTimeout(this.removePopoverTimeId) + this.removePopoverTimeId = undefined + } + } + popoverTrigger = (): void => { + this.removeEventListener('mouseenter', this.hoverPopover); + this.removeEventListener('click', this.hoverPopover); + if (this.trigger === 'hover') { + this.addEventListener('mouseenter', this.placementPosition); + } else { + this.addEventListener('click', this.placementPosition); + } + } + hoverRemovePopover = (): void => { + if (this.removePopoverTimeId) { + clearTimeout(this.removePopoverTimeId) + this.removePopoverTimeId = undefined + } + this.removePopoverTimeId = setTimeout(() => { + this.popoverContent?.style.setProperty('opacity', '0') + setTimeout(() => { + this.popoverContent?.style.setProperty('display', 'none') + }, 300); + }, 2500); + } + connectedCallback(): void { + this.setAttribute('class', 'ran-popover') + this.appendChild(this.popoverBlock) + for (const element of this.children) { + if (element.tagName === 'R-CONTENT') { + element.addEventListener('change', this.watchContent) + this.createContent(element.innerHTML) + } + } + this.popoverTrigger() + this.addEventListener('mouseenter', this.hoverPopover); + this.addEventListener('mouseleave', this.hoverRemovePopover); + } + disconnectCallback(): void { + this.removeEventListener('mouseenter', this.hoverPopover); + this.removeEventListener('mouseleave', this.hoverRemovePopover); + this.removeEventListener('click', this.hoverPopover); + } + attributeChangedCallback(n: string, o: string, v: string): void { + if (o !== v) { + if (n === 'trigger') { + this.popoverTrigger() + } + } + } +} + +function Custom() { + if (typeof document !== 'undefined' && !customElements.get('r-popover')) { + customElements.define('r-popover', Popover); + return Popover; + } else { + return createCustomError('document is undefined or r-popover is exist'); + } +} + +export default Custom(); \ No newline at end of file diff --git a/packages/ranui/components/select/index.ts b/packages/ranui/components/select/index.ts index 5f4fabe3f..51a4c849a 100644 --- a/packages/ranui/components/select/index.ts +++ b/packages/ranui/components/select/index.ts @@ -217,8 +217,8 @@ export class Select extends (HTMLElementSSR()!) { if (!this._selectionDropdown || !this._selectDropdown) return; const rect = this.getBoundingClientRect(); const { top, left, bottom, width, height, x, y, right } = rect; - this._selectionDropdown.style.setProperty('-ran-x', `${x + window.scrollX}`); - this._selectionDropdown.style.setProperty('-ran-y', `${y + window.scrollY}`); + this._selectionDropdown.style.setProperty('--ran-x', `${top + window.scrollX}`); + this._selectionDropdown.style.setProperty('--ran-y', `${left + window.scrollY}`); let selectTop = bottom + window.scrollY; let selectLeft = left + window.scrollX; this._selectionDropdown.style.setProperty('width', `${width}px`); diff --git a/packages/ranui/index.html b/packages/ranui/index.html index 6c17c01b7..57c351511 100644 --- a/packages/ranui/index.html +++ b/packages/ranui/index.html @@ -1,190 +1,197 @@ - - - - - ranui - - - -

Hello RanUI

- -

video

- -
- -
-

Progress

- -

Select

- - Mike - Tom - Lucy - - - Mike - Tom - Lucy - - - Mike - Tom - Lucy - - - Mike - Tom - Lucy - -

Button

- 主要按钮 - 警告按钮 - 默认按钮 - disable按钮 -

Icon

- - - -

Tab

- - tab1 - tab2 - tab3 - 11111 - 22222 - 33333 - 22222 - 77777 - -

Input

-
- - - -
-

Preview

- choose file to preview - - -

message

- 信息提示 - 警告提示 - 错误提示 - 成功提示 - 成功提示 -

Radar

- -

Video

- -

Modal

- - 弹窗的开关 - + + + +

Hello RanUI

+

checkbox

+ +

popover

+ + popover + +
这是内容
+
+
+

colorpicker

+ 1111111111111 +

video

+
+ +
+

Progress

+ +

Select

+ + Mike + Tom + Lucy + + + Mike + Tom + Lucy + + + Mike + Tom + Lucy + + + Mike + Tom + Lucy + +

Button

+ 主要按钮 + 警告按钮 + 默认按钮 + disable按钮 +

Icon

+ + + +

Tab

+ + tab1 + tab2 + tab3 + 11111 + 22222 + 33333 + 22222 + 77777 + +

Input

+
+ + + +
+

Preview

+ choose file to preview + + - - +

message

+ 信息提示 + 警告提示 + 错误提示 + 成功提示 + 成功提示 +

Radar

+ +

Video

+ +

Modal

+ + 弹窗的开关 + + + - + } + }; + const paths = ['/assets/js/pdf.js', '/assets/js/d3.js', '/assets/js/pdf-worker.js', '/assets/js/dimple.js']; + registerServiceWorker(paths); + + + \ No newline at end of file diff --git a/packages/ranui/index.ts b/packages/ranui/index.ts index 01d21aeaa..45c3e713a 100644 --- a/packages/ranui/index.ts +++ b/packages/ranui/index.ts @@ -15,6 +15,9 @@ export * as modal from '@/components/modal'; export * as select from '@/components/select'; export * as progress from '@/components/progress'; export * as checkbox from '@/components/checkbox' +export * as colorpicker from '@/components/colorpicker' +export * as popover from '@/components/popover' +export * as content from '@/components/content' if (typeof document !== 'undefined') { const style = document.createElement('style'); diff --git a/packages/ranui/package.json b/packages/ranui/package.json index 4f080de5a..ac8aeed53 100644 --- a/packages/ranui/package.json +++ b/packages/ranui/package.json @@ -123,6 +123,21 @@ "import": "./dist/components/player/index.js", "require": "./dist/umd/index.umd.cjs" }, + "./popover": { + "types": "./dist/components/popover/index.d.ts", + "import": "./dist/components/popover/index.js", + "require": "./dist/umd/index.umd.cjs" + }, + "./content": { + "types": "./dist/components/content/index.d.ts", + "import": "./dist/components/content/index.js", + "require": "./dist/umd/index.umd.cjs" + }, + "./colorpicker": { + "types": "./dist/components/colorpicker/index.d.ts", + "import": "./dist/components/colorpicker/index.js", + "require": "./dist/umd/index.umd.cjs" + }, "./style": { "types": "./dist/index.d.ts", "import": "./dist/style.css", diff --git a/packages/ranui/utils/color.ts b/packages/ranui/utils/color.ts new file mode 100644 index 000000000..7e0f1c531 --- /dev/null +++ b/packages/ranui/utils/color.ts @@ -0,0 +1,167 @@ +export const hsv2hsl = ( + h: number, + s: number, + v: number +): { h: number, s: number, l: number } => { + const hh = ((200 - s) * v) / 100; + return { + h: h, + s: (s * v) / (hh < 100 ? hh : 200 - hh), + l: hh / 2, + }; +} + +export const hex2alpha = (aa: string): number => { + return Math.round((parseInt('0x' + aa, 16) / 255) * 100); +} + +export const hex2hsl = (hex: string): { h: number, s: number, l: number } => { + const { h, s, v } = hex2hsv(hex); + return hsv2hsl(h, s, v); +} + +export const hex2hsv = (hex: string): { h: number, s: number, v: number } => { + const { r, g, b } = hex2rgb(hex); + return rgb2hsv(r, g, b); +} + +export const hex2rgb = (hex: string): { r: number, g: number, b: number } => { + if (hex[0] === '#') hex = hex.substr(1); + + if (hex.length === 3) { + return { + r: parseInt(hex[0] + hex[0], 16), + g: parseInt(hex[1] + hex[1], 16), + b: parseInt(hex[2] + hex[2], 16), + }; + } + + return { + r: parseInt(hex.substr(0, 2), 16), + g: parseInt(hex.substr(2, 2), 16), + b: parseInt(hex.substr(4, 2), 16), + }; +} + + +export const rgb2hsv = ( + r: number, + g: number, + b: number +): { h: number, s: number, v: number } => { + let h; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const delta = max - min; + + if (delta === 0) { + h = 0; + } else if (r === max) { + h = ((g - b) / delta) % 6; + } else if (g === max) { + h = (b - r) / delta + 2; + } else { + h = (r - g) / delta + 4; + } + + h = Math.round(h * 60); + if (h < 0) h += 360; + + const s = Math.round((max === 0 ? 0 : delta / max) * 100); + + const v = Math.round((max / 255) * 100); + + return { h, s, v }; +} + +export const hsl2hsv = ( + h: number, + s: number, + l: number +): { h: number, s: number, v: number } => { + s *= (l < 50 ? l : 100 - l) / 100; + + return { + h: h, + s: ((2 * s) / (l + s)) * 100, + v: l + s, + }; +} + +export const hsl2rgb = (h: number, s: number, l: number): { r: number, g: number, b: number } => { + const hsv = hsl2hsv(h, s, l); + return hsv2rgb(hsv.h, hsv.s, hsv.v); +} + +export const hsv2rgb = ( + h: number, + s: number, + v: number +): { r: number, g: number, b: number } => { + s = s / 100; + v = v / 100; + + let rgb = []; + const c = v * s; + const hh = h / 60; + const x = c * (1 - Math.abs((hh % 2) - 1)); + const m = v - c; + + if (hh >= 0 && hh < 1) { + rgb = [c, x, 0]; + } else if (hh >= 1 && hh < 2) { + rgb = [x, c, 0]; + } else if (hh >= 2 && hh < 3) { + rgb = [0, c, x]; + } else if (h >= 3 && hh < 4) { + rgb = [0, x, c]; + } else if (h >= 4 && hh < 5) { + rgb = [x, 0, c]; + } else if (h >= 5 && hh <= 6) { + rgb = [c, 0, x]; + } else { + rgb = [0, 0, 0]; + } + + return { + r: Math.round(255 * (rgb[0] + m)), + g: Math.round(255 * (rgb[1] + m)), + b: Math.round(255 * (rgb[2] + m)), + }; +} + + + +const convert = (num: number) => { + const hex = num.toString(16); + return hex.length === 1 ? '0' + hex : hex; +} + +export const rgb2hex = (r: number, g: number, b: number): string => { + return '#' + [convert(r), convert(g), convert(b)].join(''); +} + +export const rgba = (r: number, g: number, b: number, a: number): string => { + return 'rgba(' + [r, g, b, a / 100].join(',') + ')'; +} + +export const rgba2hex = (r: number, g: number, b: number, a: number): string => { + const rgb = rgba2rgb(r, g, b, a); + return rgb2hex(rgb.r, rgb.g, rgb.b); +} + + +export const rgba2rgb = ( + r: number, + g: number, + b: number, + a: number +): { r: number, g: number, b: number } => { + a = a / 100; + + return { + r: parseInt(`${(1 - a) * 255 + a * r}`, 10), + g: parseInt(`${(1 - a) * 255 + a * g}`, 10), + b: parseInt(`${(1 - a) * 255 + a * b}`, 10), + }; +} diff --git a/packages/ranui/vite.config.ts b/packages/ranui/vite.config.ts index 398d04a2f..416a1c172 100644 --- a/packages/ranui/vite.config.ts +++ b/packages/ranui/vite.config.ts @@ -163,6 +163,10 @@ export const es: BuildOptions = { option: resolve(__dirname, 'components/option/index.ts'), player: resolve(__dirname, 'components/player/index.ts'), progress: resolve(__dirname, 'components/progress/index.ts'), + checkbox: resolve(__dirname, 'components/checkbox/index.ts'), + colorpicker: resolve(__dirname, 'components/colorpicker/index.ts'), + popover: resolve(__dirname, 'components/popover/index.ts'), + content: resolve(__dirname, 'components/content/index.ts'), index: resolve(__dirname, 'index.ts'), }, fileName: (_: string, name: string): string => {