@spectric/ui 0.0.15 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/classes/DisposibleElement.d.ts +1 -0
  2. package/dist/classes/index.d.ts +2 -0
  3. package/dist/components/color_picker/ColorPicker.d.ts +59 -0
  4. package/dist/components/color_picker/index.d.ts +0 -0
  5. package/dist/components/index.d.ts +2 -0
  6. package/dist/components/input.d.ts +3 -1
  7. package/dist/components/table/body.d.ts +2 -2
  8. package/dist/components/table/cell.d.ts +5 -2
  9. package/dist/components/table/header.d.ts +9 -3
  10. package/dist/components/table/table.d.ts +18 -6
  11. package/dist/components/table/virtualBody.d.ts +2 -2
  12. package/dist/components/tooltip/popover.d.ts +85 -0
  13. package/dist/components/tooltip/tooltip.d.ts +14 -15
  14. package/dist/custom-elements.json +83 -8
  15. package/dist/index.d.ts +58 -0
  16. package/dist/index.es.js +2640 -2243
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +183 -132
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/style.css +1 -1
  21. package/dist/utils/index.d.ts +3 -0
  22. package/package.json +6 -2
  23. package/src/classes/DisposibleElement.ts +3 -0
  24. package/src/classes/index.ts +2 -0
  25. package/src/components/color_picker/ColorPicker.css +4 -0
  26. package/src/components/color_picker/ColorPicker.ts +244 -0
  27. package/src/components/color_picker/index.ts +1 -0
  28. package/src/components/index.ts +3 -1
  29. package/src/components/input.css +38 -1
  30. package/src/components/input.ts +34 -7
  31. package/src/components/table/__tests__/table.spec.ts +91 -0
  32. package/src/components/table/body.ts +2 -2
  33. package/src/components/table/cell.ts +34 -8
  34. package/src/components/table/header.css +54 -0
  35. package/src/components/table/header.ts +144 -49
  36. package/src/components/table/table.css +20 -33
  37. package/src/components/table/table.ts +50 -16
  38. package/src/components/table/virtualBody.ts +2 -2
  39. package/src/components/tooltip/popover.ts +221 -0
  40. package/src/components/tooltip/tooltip.css +21 -16
  41. package/src/components/tooltip/tooltip.ts +17 -124
  42. package/src/index.ts +8 -1
  43. package/src/stories/fixtures/ExampleContent.ts +3 -3
  44. package/src/stories/table.stories.ts +5 -5
  45. package/src/utils/index.ts +3 -0
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- spectric-input{--input-color: var(--spectric-input-color, #f4f4f4);--border-radius: var(--spectric-border-radius, .4em);--input-bottom: var(--spectric-input-bottom, var(--spectric-button-primary, #a8a8a8));--input-bottom-focused: var(--primary, #1ea7fd);--text-on-color: var(--spectric-text-on-color, #ffffff);--text-on-color-disabled: var(--spectric-text-on-color-disabled, #8d8d8d);--text-placeholder: rgba(22, 22, 22, .4);--text-primary: var(--spectric-text-primary, #161616);--text-secondary: var(--spectric-text-secondary, #525252)}spectric-input .inputWrapper{color:var(--text-secondary)}spectric-input .inputWrapper input{box-sizing:border-box;margin:0;vertical-align:baseline;font-size:.875rem;font-weight:400;line-height:1.28572;letter-spacing:.16px;outline:transparent solid 2px;outline-offset:-2px;border:none;padding:0 1rem;background-color:var(--input-color);color:var(--text-primary, #161616);font-family:inherit;inline-size:100%;block-size:2.5rem}spectric-input .inputWrapper .inputContainer:active:after,spectric-input .inputContainer:focus-within:after{border-bottom-color:var(--input-bottom-focused);width:calc(100% - 5px);transition:width .4s ease-in-out}spectric-input .inputWrapper input:read-only{background-color:transparent;border-bottom-color:var(--border-disabled)}spectric-input .inputContainer{position:relative;border-radius:var(--border-radius);overflow:hidden}spectric-input .inputContainer:after{content:"";width:0px;transition:background-color .4s cubic-bezier(.2,0,.38,.9),border-bottom-color .4s cubic-bezier(.2,0,.38,.9);border-bottom-color:var(--input-bottom);border-bottom-style:solid;border-bottom-width:1px;position:absolute;left:2.5px;bottom:0}spectric-input #helper-text{height:18px}spectric-input[variant=password] spectric-button{position:absolute;right:4px;bottom:3px}spectric-input .checkbox{display:flex;justify-self:center}spectric-query{font-family:monospace}spectric-query .autocomplete{color:var(--spectric-text-primary, #161616);border-radius:0em 0em var(--spectric-border-radius, .4em) var(--spectric-border-radius, .4em);background-color:var(--spectric-background, #ffffff);border:1px solid var(--spectric-background-hover, rgba(141, 141, 141, .12));max-height:300px;border-top:0px;margin:-18px 0 0;position:fixed;top:anchor(bottom);justify-self:anchor-center;text-align:center}spectric-query .autocomplete .optiontype{float:left;max-width:10px}spectric-query .autocomplete .label{position:absolute;right:0}spectric-query .autocomplete .option.active,spectric-query .autocomplete .option:hover{background-color:var(--spectric-background-hover, rgba(141, 141, 141, .12));border-bottom:1px solid var(--primary, #1ea7fd)}spectric-query .autocomplete .option{border-bottom:1px solid transparent;padding:8px}.query-bar-date-quick-select{display:flex;justify-content:space-evenly}spectric-pagination .spectric-pagination-container{display:flex;justify-content:space-between;align-items:center}spectric-pagination .spectric-pagination-text{flex-grow:1;text-align:center}spectric-table{display:flex;flex-direction:column;overflow:hidden}spectric-table .table-wrapper{overflow:auto;flex-grow:1;position:relative}spectric-table tr{text-align:center}spectric-table tr.odd{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 90%)}spectric-table tr:hover{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 70%)}spectric-table-header{display:table-header-group;font-weight:700;position:sticky;top:0;left:0;z-index:1;background:var(--spectric-background, #ffffff)}spectric-table-header td{vertical-align:middle}spectric-table tr{line-height:var(--rowHeight)}spectric-table td{height:var(--rowHeight)}spectric-table-header .header-contents{position:relative}spectric-table-header .header-contents .sort-direction{position:absolute;right:0;top:calc(50% - 8px)}spectric-table-header .header-contents.sortable{cursor:pointer;padding-right:15px}spectric-table-header .header-contents.sortable:hover .sort-direction.none:before{content:"⮁"}spectric-table div[role=table]{display:table;min-width:100%}spectric-table-cell{display:contents;vertical-align:middle}spectric-table-cell td{position:relative}spectric-table td:hover:has(.filterable){border:1px solid var(--spectric-primary, #1ea7fd)}spectric-table td{border:1px solid transparent}spectric-table-cell .table-cell-actions{position:absolute;display:flex;width:100%;flex-direction:row-reverse;visibility:hidden;top:-10px}spectric-table-cell td:hover .table-cell-actions{visibility:unset}spectric-table .table-checkbox-single spectric-button{--button-border-radius: 50%}spectric-input.table-checkbox-single[checked] spectric-button{--text-on-color: transparent;border-radius:50%;position:relative}spectric-input.table-checkbox-single[checked] spectric-button:before{position:absolute;content:" ";height:50%;width:50%;left:25%;top:25%;border-radius:50%;z-index:1;box-shadow:0 0 0 4px var(--input-color)}spectric-table-body{display:table-row-group}spectric-table-virtual-body{display:contents}spectric-table-virtual-body .virtual-scroll-spacer td{padding:0;border:0px}.spectric-tooltip-portal{position:fixed;z-index:9999;pointer-events:none;--spectric-tooltip-background: color-mix(in srgb,var(--spectric-background-inverse,#f4f4f4) 100%,var(--spectric-primary,#1ea7fd) 90%) }.spectric-tooltip-portal .tooltip-container{display:flex;justify-content:center;align-items:center}.spectric-tooltip-portal.top .tooltip-container{flex-direction:column-reverse}.spectric-tooltip-portal.bottom .tooltip-container{flex-direction:column}.spectric-tooltip-portal.left .tooltip-container{flex-direction:row-reverse}.spectric-tooltip-portal .tooltip-content{background:var(--spectric-tooltip-background);border-radius:var(--spectric-border-radius,.4em);box-shadow:0 0 .01em .01em color-mix(in srgb,var(--spectric-background-hover,rgba(141, 141, 141, .12)) 90%,var(--spectric-text-on-color,#ffffff) 90%);padding:.2em;color:var(--spectric-text-on-color,#ffffff)}.spectric-tooltip-portal .tooltip-caret{background:var(--spectric-tooltip-background)}.spectric-tooltip-portal.top .tooltip-caret,.spectric-tooltip-portal.bottom .tooltip-caret{inline-size:.75rem;block-size:.374rem}.spectric-tooltip-portal.left .tooltip-caret,.spectric-tooltip-portal.right .tooltip-caret{inline-size:.375rem;block-size:.75rem}.spectric-tooltip-portal.top .tooltip-caret{clip-path:polygon(0 0,50% 100%,100% 0)}.spectric-tooltip-portal.bottom .tooltip-caret{clip-path:polygon(0 100%,50% 0,100% 100%)}.spectric-tooltip-portal.left .tooltip-caret{clip-path:polygon(0 0,100% 50%,0 100%)}.spectric-tooltip-portal.right .tooltip-caret{clip-path:polygon(0 50%,100% 0,100% 100%)}
1
+ spectric-input{--input-color: var(--spectric-input-color, #f4f4f4);--border-radius: var(--spectric-border-radius, .4em);--input-bottom: var(--spectric-input-bottom, var(--spectric-button-primary, #a8a8a8));--input-bottom-focused: var(--primary, #1ea7fd);--text-on-color: var(--spectric-text-on-color, #ffffff);--text-on-color-disabled: var(--spectric-text-on-color-disabled, #8d8d8d);--text-placeholder: rgba(22, 22, 22, .4);--text-primary: var(--spectric-text-primary, #161616);--text-secondary: var(--spectric-text-secondary, #525252)}spectric-input .inputWrapper{color:var(--text-secondary)}spectric-input.hue-gradient input[type=range]::-webkit-slider-runnable-track{background:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.saturation-lightness-grid{background:linear-gradient(to right,hsl(var(--accent-color),0%,50%),hsl(var(--accent-color),100%,50%)),linear-gradient(to top,hsl(var(--accent-color),100%,0%),hsl(var(--accent-color),100%,50%),hsl(var(--accent-color),100%,100%))}spectric-input.alpha-gradient input[type=range]::-webkit-slider-runnable-track{background-image:linear-gradient(to right,#fff0,#fff)}spectric-input.alpha-gradient input[type=range],spectric-input.hue-gradient input[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none}spectric-input .inputWrapper input{box-sizing:border-box;margin:0;vertical-align:baseline;font-size:.875rem;font-weight:400;line-height:1.28572;letter-spacing:.16px;outline:transparent solid 2px;outline-offset:-2px;border:none;padding:0 1rem;background-color:var(--input-color);color:var(--text-primary, #161616);font-family:inherit;inline-size:100%;block-size:2.5rem}spectric-input .inputWrapper .inputContainer:active:after,spectric-input .inputContainer:focus-within:after{border-bottom-color:var(--input-bottom-focused);width:calc(100% - 5px);transition:width .4s ease-in-out}spectric-input .inputWrapper input:read-only{background-color:transparent;border-bottom-color:var(--border-disabled)}spectric-input .inputContainer{position:relative;border-radius:var(--border-radius);overflow:hidden}spectric-input .inputContainer:after{content:"";width:0px;transition:background-color .4s cubic-bezier(.2,0,.38,.9),border-bottom-color .4s cubic-bezier(.2,0,.38,.9);border-bottom-color:var(--input-bottom);border-bottom-style:solid;border-bottom-width:1px;position:absolute;left:2.5px;bottom:0}spectric-input #helper-text{height:18px}spectric-input #helper-text.hidden{display:none}spectric-input[variant=password] spectric-button{position:absolute;right:4px;bottom:3px}spectric-input .checkbox{display:flex;justify-self:center}spectric-input spectric-colorpicker{display:block}spectric-query{font-family:monospace}spectric-query .autocomplete{color:var(--spectric-text-primary, #161616);border-radius:0em 0em var(--spectric-border-radius, .4em) var(--spectric-border-radius, .4em);background-color:var(--spectric-background, #ffffff);border:1px solid var(--spectric-background-hover, rgba(141, 141, 141, .12));max-height:300px;border-top:0px;margin:-18px 0 0;position:fixed;top:anchor(bottom);justify-self:anchor-center;text-align:center}spectric-query .autocomplete .optiontype{float:left;max-width:10px}spectric-query .autocomplete .label{position:absolute;right:0}spectric-query .autocomplete .option.active,spectric-query .autocomplete .option:hover{background-color:var(--spectric-background-hover, rgba(141, 141, 141, .12));border-bottom:1px solid var(--primary, #1ea7fd)}spectric-query .autocomplete .option{border-bottom:1px solid transparent;padding:8px}.query-bar-date-quick-select{display:flex;justify-content:space-evenly}spectric-pagination .spectric-pagination-container{display:flex;justify-content:space-between;align-items:center}spectric-pagination .spectric-pagination-text{flex-grow:1;text-align:center}spectric-table{display:flex;flex-direction:column;overflow:hidden;line-height:1}spectric-table .table-wrapper{overflow:auto;flex-grow:1;position:relative}spectric-table tr{text-align:center}spectric-table tr.odd{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 90%)}spectric-table spectric-table-virtual-body tr:hover{background-color:color-mix(in srgb,var(--spectric-primary, #1ea7fd),transparent 70%)}spectric-table tr{height:var(--rowHeight)}spectric-table td{height:var(--rowHeight);padding:1px;border:1px solid transparent}spectric-table div[role=table]{display:table;min-width:100%}spectric-table-cell{display:contents;vertical-align:middle}spectric-table-cell td{position:relative}spectric-table td:hover:has(.filterable){border:1px solid var(--spectric-primary, #1ea7fd)}spectric-table-cell .table-cell-actions{position:absolute;display:flex;width:100%;flex-direction:row-reverse;visibility:hidden;top:-10px;z-index:1}spectric-table-cell td:hover .table-cell-actions{visibility:unset}spectric-table .table-checkbox-single spectric-button{--button-border-radius: 50%}spectric-input.table-checkbox-single[checked] spectric-button{--text-on-color: transparent;border-radius:50%;position:relative}spectric-input.table-checkbox-single[checked] spectric-button:before{position:absolute;content:" ";height:50%;width:50%;left:25%;top:25%;border-radius:50%;z-index:1;box-shadow:0 0 0 4px var(--input-color)}spectric-table .cell-contents{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--lineClamp,1);-webkit-box-pack:center;overflow:hidden;text-overflow:ellipsis;font-size:var(--fontSize);height:calc(var(--lineClamp) * var(--fontSize))}spectric-table-header{display:table-header-group;font-weight:700;position:sticky;top:0;left:0;z-index:1;background:var(--spectric-background, #ffffff)}spectric-table-header td{vertical-align:middle;position:relative}spectric-table-header .header-contents{position:relative}spectric-table-header .header-contents .sort-direction{position:absolute;right:2px;top:calc(50% - 8px)}spectric-table-header .header-contents.sortable{cursor:pointer;padding-right:15px}spectric-table-header .header-contents.resizing{-webkit-user-select:none;user-select:none}spectric-table-header .header-contents.sortable:hover .sort-direction.none:before{content:"⮁"}spectric-table-header td .header-resize-handle{width:2px;position:absolute;right:-1px;top:1px;visibility:hidden;background-color:var(--spectric-primary, #1ea7fd);height:100%;cursor:ew-resize;z-index:1}spectric-table-header td:hover .header-resize-handle{visibility:visible}spectric-table-header td:hover:has(*.header-resize-handle){border-bottom:1px solid var(--spectric-primary, #1ea7fd)}spectric-table-body{display:table-row-group}spectric-table-virtual-body{display:contents}spectric-table-virtual-body .virtual-scroll-spacer td{padding:0;border:0px}.spectric-popover-portal{position:fixed;z-index:9999;--spectric-tooltip-background-color: var(--spectric-tooltip-background, #000000);--spectric-tooltip-text-color: var(--spectric-tooltip-text, var(--spectric-text-primary, white));--spectric-tooltip-accent-color: var(--spectric-tooltip-accent, var(--spectric-primary, #1ea7fd))}.spectric-popover-portal.spectric-tooltip-portal{pointer-events:none}.spectric-popover-portal .tooltip-container{display:flex;justify-content:center;align-items:center}.spectric-popover-portal.top .tooltip-container{flex-direction:column-reverse}.spectric-popover-portal.bottom .tooltip-container{flex-direction:column}.spectric-popover-portal.left .tooltip-container{flex-direction:row-reverse}.spectric-popover-portal .tooltip-content{background:var(--spectric-tooltip-background-color);border-radius:var(--spectric-border-radius,.4em);border:1px solid color-mix(in srgb,var(--spectric-tooltip-background-color) 90%,var(--spectric-tooltip-accent-color) 90%);box-shadow:0 0 .01em .01em color-mix(in srgb,var(--spectric-tooltip-accent-color) 90%,var(--spectric-text-on-color,#ffffff) 90%);padding:.2em;color:var(--spectric-tooltip-text-color)}.spectric-popover-portal .tooltip-caret{background:color-mix(in srgb,var(--spectric-tooltip-background-color) 90%,var(--spectric-tooltip-accent-color) 90%)}.spectric-tooltip-portal.top .tooltip-caret,.spectric-tooltip-portal.bottom .tooltip-caret{inline-size:.75rem;block-size:.374rem}.spectric-tooltip-portal.left .tooltip-caret,.spectric-tooltip-portal.right .tooltip-caret{inline-size:.375rem;block-size:.75rem}.spectric-popover-portal.top .tooltip-caret{clip-path:polygon(0 0,50% 100%,100% 0)}.spectric-popover-portal.bottom .tooltip-caret{clip-path:polygon(0 100%,50% 0,100% 100%)}.spectric-popover-portal.left .tooltip-caret{clip-path:polygon(0 0,100% 50%,0 100%)}.spectric-popover-portal.right .tooltip-caret{clip-path:polygon(0 50%,100% 0,100% 100%)}.color-picker-footer{display:flex;justify-content:space-evenly}
@@ -0,0 +1,3 @@
1
+ export * from './debounce';
2
+ export * from './once';
3
+ export * from './spread';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectric/ui",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "main": "./dist/index.es.js",
6
6
  "module": "./dist/index.es.js",
@@ -13,14 +13,18 @@
13
13
  "scripts": {
14
14
  "build": "tsc && vite build",
15
15
  "start": "storybook dev -p 6006",
16
+ "test-setup": "storybook dev -p 6006 --no-open --ci --loglevel verbose",
16
17
  "storybook:build": "web-component-analyzer src --outFiles .storybook/build/custom-elements.json && storybook build --output-dir public",
17
- "release": "npm version patch && git push --follow-tags"
18
+ "release": "npm version patch && git push --follow-tags",
19
+ "test": "playwright test --reporter=list --timeout 90000",
20
+ "type-check": "tsc --noemit"
18
21
  },
19
22
  "dependencies": {
20
23
  "lit": "^3.2.1"
21
24
  },
22
25
  "devDependencies": {
23
26
  "@chromatic-com/storybook": "^3.2.2",
27
+ "@playwright/test": "^1.55.0",
24
28
  "@storybook/addon-essentials": "^8.4.6",
25
29
  "@storybook/blocks": "^8.4.6",
26
30
  "@storybook/client-api": "^7.6.17",
@@ -5,6 +5,9 @@ export interface IDisposable {
5
5
  }
6
6
  type DisposableTarget = HTMLElement | Promise<HTMLElement> | (() => HTMLElement)
7
7
  let listeners = 0;
8
+ export const getListeners = () => {
9
+ return listeners
10
+ }
8
11
  class DomListener implements IDisposable {
9
12
 
10
13
  private _handler?: (e: any) => void;
@@ -0,0 +1,2 @@
1
+ export * from "./BitArray"
2
+ export * from "./DisposibleElement"
@@ -0,0 +1,4 @@
1
+ .color-picker-footer{
2
+ display: flex;
3
+ justify-content: space-evenly;
4
+ }
@@ -0,0 +1,244 @@
1
+ import { html, LitElement, PropertyValues, TemplateResult } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
4
+ import { PopoverElement } from '../tooltip/popover';
5
+ import { createRef, ref } from 'lit/directives/ref.js';
6
+ import { DomEvent, } from '../table';
7
+ import { SpectricInput } from '../input';
8
+ import "./ColorPicker.css"
9
+ export interface ColorPickerProps {
10
+ /**Color in hex*/
11
+ value?: string,
12
+ showAlpha?: boolean
13
+ }
14
+
15
+ @customElement('spectric-colorpicker')
16
+ export class SpectricColorPicker extends LitElement implements ColorPickerProps {
17
+ @property({ type: String, reflect: true })
18
+ value = "#FF0000FF"
19
+ @property({ type: Boolean, reflect: true })
20
+ showAlpha = true
21
+ @state()
22
+ private hue: number = 0;
23
+ @state()
24
+ private alpha: number = 1;
25
+ @state()
26
+ private saturation: number = 1;
27
+ @state()
28
+ private lightness: number = 1;
29
+ private canvas = createRef<HTMLCanvasElement>()
30
+ private original?: string;
31
+ /**is mouse down on saturation/light canvas? */
32
+ private sldown: boolean = false;
33
+
34
+ protected createRenderRoot(): HTMLElement | DocumentFragment {
35
+ return this
36
+ }
37
+
38
+ protected update(changedProperties: PropertyValues): void {
39
+ if (changedProperties.has("value")) {
40
+ let { h, s, l, a } = hexToHsl(this.value)
41
+ this.hue = h
42
+ this.saturation = s;
43
+ this.lightness = l;
44
+ this.alpha = a
45
+ this.renderHueSaturationGrid()
46
+ }
47
+ super.update(changedProperties)
48
+ }
49
+ private renderHueSaturationGrid() {
50
+ console.log("canvas render")
51
+ if (!this.canvas.value) {
52
+ return
53
+ }
54
+ let canvas: HTMLCanvasElement = this.canvas.value;
55
+ let ctx = canvas.getContext("2d");
56
+ if (!ctx) {
57
+ return
58
+ }
59
+ let xpix = this.canvas.value.width / 100
60
+ let ypix = this.canvas.value.height / 100
61
+ let x = 0,
62
+ y = 0; //pixel size
63
+ for (let m of hslGen(this.hue)) {
64
+ ctx.fillStyle = m;
65
+ ctx.fillRect(x, y, xpix, ypix); //Need to made this a 2d saturation, light graph
66
+ x += xpix;
67
+ x = x % (xpix * 101);
68
+ if (!x)
69
+ y += ypix;
70
+ }
71
+ }
72
+ _handleSaturationLightnessClick = (e: PointerEvent) => {
73
+ if (!this.canvas.value) {
74
+ return
75
+ }
76
+ let { offsetX, offsetY } = e
77
+ this.saturation = offsetX / this.canvas.value.width
78
+ this.lightness = (this.canvas.value.height - offsetY) / this.canvas.value.height
79
+ this.updateValue()
80
+ }
81
+ _handleHueChange = (e: DomEvent<SpectricInput>) => {
82
+ this.hue = (parseInt(String(e.target.value)) / 100) * 360
83
+ e.target.style.setProperty("accent-color", `hsl(${this.hue}deg 100% 50%)`)
84
+ this.renderHueSaturationGrid()
85
+ this.updateValue()
86
+ }
87
+ private updateValue() {
88
+ this.value = hslToHex(this.hue, this.saturation * 100, this.lightness * 100) + (Math.round(this.alpha * 255).toString(16).padStart(2, "0"))
89
+ }
90
+ _handleAlphaChange = (e: DomEvent<SpectricInput>) => {
91
+ this.alpha = parseInt(String(e.target.value)) / 100
92
+ console.log(this.alpha, e)
93
+ this.updateValue()
94
+ }
95
+ _handleApply = () => {
96
+ console.log(this.hue, this.saturation, this.lightness, this.alpha)
97
+ this.updateValue()
98
+ console.log(this.value)
99
+ this.querySelector<PopoverElement>("spectric-popover")?.hidePopover()
100
+ }
101
+ _cancel = () => {
102
+ if (this.original) {
103
+ this.value = this.original
104
+ }
105
+ this.querySelector<PopoverElement>("spectric-popover")?.hidePopover()
106
+ }
107
+ _openPopover = async () => {
108
+ this.original = this.value
109
+ this.querySelector<PopoverElement>("spectric-popover")?.showPopover()
110
+ while (!this.canvas.value) {
111
+ await new Promise(resolve => setTimeout(resolve, 100))
112
+ }
113
+ this.renderHueSaturationGrid()
114
+ }
115
+ protected getPopover(): TemplateResult {
116
+ return html`
117
+ <spectric-input label="Hue" class="hue-gradient" variant="range" .value=${this.hue || 0} style="accent-color:hsl(${this.hue}deg 100% 50%)" @change=${this._handleHueChange}></spectric-input>
118
+ <canvas ${ref(this.canvas)} width=200 height=100 class="saturation-lightness-grid" @click=${this._handleSaturationLightnessClick}
119
+ @mousedown=${() => { this.sldown = true }}
120
+ @mouseup=${() => this.sldown = false}
121
+ @mousemove=${(e: PointerEvent) => { if (this.sldown) { this._handleSaturationLightnessClick(e) } }}
122
+ ></canvas>
123
+ <spectric-input class="alpha-gradient" ?hidden=${!this.showAlpha} label="Opacity" variant="range" .value=${(this.alpha || 1) * 100} @change=${this._handleAlphaChange}></spectric-input>
124
+ <div class="color-picker-footer">
125
+ <spectric-button @click=${this._handleApply}>Apply</spectric-button>
126
+ <spectric-button variant="secondary" @click=${this._cancel}>Cancel</spectric-button>
127
+ </div>
128
+ `
129
+ }
130
+
131
+ protected render(): unknown {
132
+ return html`
133
+ <spectric-button variant="text" @click=${this._openPopover}>
134
+ <spectric-popover .text=${this.getPopover()} icon variant="text"></spectric-popover>
135
+ <div style="width:15px;height:15px;background-color:${this.value}"></div>
136
+ </spectric-button>
137
+ `;
138
+ }
139
+ }
140
+
141
+ function* hslGen(hue: number) {
142
+ for (let l = 100; l >= 0; l--) {
143
+ for (let s = 0; s <= 100; s++) {
144
+ yield `hsl(${hue}, ${s}%, ${l}%)`;
145
+ }
146
+ }
147
+ }
148
+ function hslToHex(h: number, s: number, l: number) {
149
+ l /= 100;
150
+ const a = s * Math.min(l, 1 - l) / 100;
151
+ const f = (n: number) => {
152
+ const k = (n + h / 30) % 12;
153
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
154
+ return Math.round(255 * color).toString(16).padStart(2, '0'); // convert to Hex and prefix "0" if needed
155
+ };
156
+ return `#${f(0)}${f(8)}${f(4)}`;
157
+ }
158
+ function hexToHsl(hex: string) {
159
+ // Convert hex to RGB
160
+ let r = 0, g = 0, b = 0, a = 255;
161
+ if (hex.length === 4 || hex.length === 5) { // Handle shorthand hex codes like #F0C
162
+ r = parseInt(hex[1] + hex[1], 16);
163
+ g = parseInt(hex[2] + hex[2], 16);
164
+ b = parseInt(hex[3] + hex[3], 16);
165
+ if (hex.length === 5) {
166
+ a = parseInt(hex[4] + hex[4], 16);
167
+ }
168
+ } else if (hex.length === 7 || hex.length === 9) { // Handle full hex codes like #FF00CC
169
+ r = parseInt(hex.substring(1, 3), 16);
170
+ g = parseInt(hex.substring(3, 5), 16);
171
+ b = parseInt(hex.substring(5, 7), 16);
172
+ if (hex.length === 9) {
173
+ a = parseInt(hex.substring(7, 9), 16);
174
+ }
175
+ }
176
+
177
+ // Normalize RGB values to a range of 0-1
178
+ r /= 255;
179
+ g /= 255;
180
+ b /= 255;
181
+ a /= 255
182
+ // Find min and max values among R, G, B
183
+ const max = Math.max(r, g, b);
184
+ const min = Math.min(r, g, b);
185
+
186
+ let h = 0, s = 0, l = (max + min) / 2;
187
+
188
+ if (max === min) {
189
+ // Achromatic (grayscale)
190
+ h = 0;
191
+ s = 0;
192
+ } else {
193
+ const d = max - min;
194
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
195
+
196
+ switch (max) {
197
+ case r:
198
+ h = (g - b) / d + (g < b ? 6 : 0);
199
+ break;
200
+ case g:
201
+ h = (b - r) / d + 2;
202
+ break;
203
+ case b:
204
+ h = (r - g) / d + 4;
205
+ break;
206
+ }
207
+ h /= 6;
208
+ }
209
+
210
+ // Convert Hue to degrees
211
+ h = Math.round(h * 360);
212
+
213
+ //leave sla as 0-1
214
+ return { h, s, l, a };
215
+ }
216
+ export interface ColorPickerEventMap {
217
+ 'change': (event: CustomEvent<ColorPickerProps>) => void
218
+ }
219
+
220
+ declare global {
221
+
222
+ interface HTMLElementTagNameMap {
223
+ "spectric-colorpicker": HTMLElementTagWithEvents<SpectricColorPicker, ColorPickerEventMap>
224
+ }
225
+
226
+ namespace JSX {
227
+ interface IntrinsicElements {
228
+ /**
229
+ * {@link SpectricColorPicker}
230
+ */
231
+ "spectric-colorpicker": ReactElementWithPropsAndEvents<SpectricColorPicker, ColorPickerProps, ColorPickerEventMap>;
232
+ }
233
+ }
234
+ namespace React {
235
+ namespace JSX {
236
+ interface IntrinsicElements {
237
+ /**
238
+ * {@link SpectricColorPicker}
239
+ */
240
+ "spectric-colorpicker": ReactElementWithPropsAndEvents<SpectricColorPicker, ColorPickerProps, ColorPickerEventMap>;
241
+ }
242
+ }
243
+ }
244
+ }
@@ -0,0 +1 @@
1
+ import "./ColorPicker"
@@ -10,4 +10,6 @@ export * from "./dialog"
10
10
  export * from "./splitview"
11
11
  export * from "./pagination"
12
12
  export * from "./table"
13
- export * from "./tooltip"
13
+ export * from "./tooltip"
14
+ export * from "./types"
15
+ export * from "./color_picker"
@@ -14,7 +14,39 @@ spectric-input {
14
14
  spectric-input .inputWrapper {
15
15
  color: var(--text-secondary)
16
16
  }
17
+ spectric-input.hue-gradient input[type="range"]::-webkit-slider-runnable-track {
18
+ background: linear-gradient(
19
+ to right,
20
+ hsl(0, 100%, 50%), /* Red */
21
+ hsl(60, 100%, 50%), /* Yellow */
22
+ hsl(120, 100%, 50%), /* Green */
23
+ hsl(180, 100%, 50%), /* Cyan */
24
+ hsl(240, 100%, 50%), /* Blue */
25
+ hsl(300, 100%, 50%), /* Magenta */
26
+ hsl(360, 100%, 50%) /* Red (completes the circle) */
27
+ )
28
+ }
17
29
 
30
+ .saturation-lightness-grid {
31
+ /* Assuming a fixed hue, e.g., 240 (blue) */
32
+ background: linear-gradient(to right,
33
+ hsl(var(--accent-color), 0%, 50%), /* From desaturated */
34
+ hsl(var(--accent-color), 100%, 50%) /* To fully saturated */
35
+ ),
36
+ linear-gradient(to top,
37
+ hsl(var(--accent-color), 100%, 0%), /* From dark */
38
+ hsl(var(--accent-color), 100%, 50%), /* To mid-lightness */
39
+ hsl(var(--accent-color), 100%, 100%) /* To bright */
40
+ );
41
+ }
42
+ spectric-input.alpha-gradient input[type="range"]::-webkit-slider-runnable-track {
43
+ background-image:
44
+ /* Alpha gradient from transparent to opaque */
45
+ linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
46
+ }
47
+ spectric-input.alpha-gradient input[type="range"],spectric-input.hue-gradient input[type="range"]{ -webkit-appearance: none; /* For WebKit browsers (Chrome, Safari) */
48
+ -moz-appearance: none; /* For Mozilla Firefox */
49
+ appearance: none; /* Standard property */}
18
50
  spectric-input .inputWrapper input {
19
51
  box-sizing: border-box;
20
52
  margin: 0px;
@@ -67,7 +99,9 @@ spectric-input .inputContainer::after {
67
99
  spectric-input #helper-text {
68
100
  height: 18px;
69
101
  }
70
-
102
+ spectric-input #helper-text.hidden {
103
+ display: none;
104
+ }
71
105
  spectric-input[variant="password"] spectric-button {
72
106
  position: absolute;
73
107
  right: 4px;
@@ -77,4 +111,7 @@ spectric-input[variant="password"] spectric-button {
77
111
  spectric-input .checkbox{
78
112
  display: flex;
79
113
  justify-self: center;
114
+ }
115
+ spectric-input spectric-colorpicker{
116
+ display: block;
80
117
  }
@@ -1,9 +1,11 @@
1
1
  import { html, LitElement, PropertyValues, TemplateResult } from 'lit';
2
2
  import "./input.css"
3
- import { customElement, property, query } from 'lit/decorators.js';
3
+ import { customElement, eventOptions, property, query } from 'lit/decorators.js';
4
4
  import { ifDefined } from 'lit/directives/if-defined.js';
5
5
  import { ReactElementWithPropsAndEvents } from './types';
6
6
  import { ButtonSizesTypes } from './Button';
7
+ import { DomEvent } from './table';
8
+ import { SpectricColorPicker } from './color_picker/ColorPicker';
7
9
 
8
10
  export enum InputVariants {
9
11
  Text = 'text',
@@ -17,7 +19,8 @@ export enum InputVariants {
17
19
  file = "file",//display drop area
18
20
  hidden = "hidden",//display drop area
19
21
  password = "password",
20
- checkbox = "checkbox"
22
+ checkbox = "checkbox",
23
+ range = "range"
21
24
 
22
25
  }
23
26
  type InputVariantsTypes = `${InputVariants}`
@@ -232,6 +235,11 @@ export class SpectricInput extends LitElement implements InputProps {
232
235
  this.dispatchEvent(new Event("change", { bubbles: true }))
233
236
  }
234
237
  }
238
+ @eventOptions({ capture: true })
239
+ _handleChange(e: Event) {
240
+ e.stopPropagation()
241
+ this.dispatchEvent(new Event("change", { bubbles: true }))
242
+ }
235
243
  protected render(): unknown {
236
244
  switch (this.variant) {
237
245
  case InputVariants.Text:
@@ -243,7 +251,7 @@ export class SpectricInput extends LitElement implements InputProps {
243
251
  case InputVariants.date://replace with custom date picker
244
252
  case InputVariants.datetime://replace with custom date picker
245
253
  case InputVariants.file://replace with drag and drop location and custom select file button
246
- case InputVariants.color: //replace with custom color picker
254
+ case InputVariants.range:
247
255
  return html`
248
256
  <div class="inputWrapper">
249
257
  <div class="text-input__label-helper-wrapper">
@@ -271,9 +279,7 @@ export class SpectricInput extends LitElement implements InputProps {
271
279
  .value="${this._value as string}"
272
280
  maxlength="${ifNonEmpty(this.maxCount > 0 ? this.maxCount : undefined)}"
273
281
  @input="${this._handleInput}"
274
- @change=${() => {
275
- this.dispatchEvent(new Event("change", { bubbles: true }))
276
- }}
282
+ @change=${this._handleChange}
277
283
  />
278
284
 
279
285
  ${this.variant === String(InputVariants.password) && this.showPasswordVisibilityToggle
@@ -290,6 +296,7 @@ export class SpectricInput extends LitElement implements InputProps {
290
296
  </div>
291
297
  <div
292
298
  id="helper-text"
299
+ class="${this.helperText || this.invalid ? "" : "hidden"}"
293
300
  >
294
301
  <slot name="helper-text"> ${this.invalid ? this.invalidText : this.helperText} </slot>
295
302
  </div>
@@ -298,8 +305,28 @@ export class SpectricInput extends LitElement implements InputProps {
298
305
  </div>
299
306
  `;
300
307
 
308
+ case InputVariants.color: //replace with custom color picker
309
+ return html`<div class="inputWrapper">
310
+ <div class="text-input__label-helper-wrapper">
311
+ <div class="--text-input__label-wrapper">
312
+ ${this.label} ${this.maxCount > 0 && this._value ? `${(this._value as String).length}/${this.maxCount}` : null}
313
+ </div>
314
+ </div>
315
+ <div class="fieldwrapper">
316
+ <div ?data-invalid="${this.invalid}" class="inputContainer">
317
+ <spectric-colorpicker @change=${(e: DomEvent<SpectricColorPicker>) => { this.value = e.target.value; this._handleChange(e) }}></spectric-colorpicker>
318
+ </div>
319
+ <div
320
+ id="helper-text"
321
+ class="${this.helperText || this.invalid ? "" : "hidden"}"
322
+ >
323
+ <slot name="helper-text"> ${this.invalid ? this.invalidText : this.helperText} </slot>
324
+ </div>
325
+
326
+ </div>
327
+ </div>`
301
328
  case InputVariants.hidden:
302
- return html`<input type="hidden"></input>`
329
+ return html`<input type="hidden"/>`
303
330
 
304
331
  case InputVariants.checkbox:
305
332
 
@@ -0,0 +1,91 @@
1
+ import { test, expect } from '@playwright/test';
2
+ import type { SpectricTableElement, TableEvents } from '../index';
3
+ import type SpectricModule from "../../../index.ts"
4
+ test.beforeEach(async ({ page }) => {
5
+ await page.addInitScript(() => {
6
+ //@ts-ignore
7
+ window.awaitEvent = function awaitEvent(target, event, trigger) {
8
+ return new Promise((resolve, reject) => {
9
+ const handleEvent = (...args) => {
10
+ //@ts-ignore
11
+ target.removeEventListener(event, this)
12
+ resolve(args)
13
+ }
14
+ //@ts-ignore
15
+ target.addEventListener(event, handleEvent)
16
+ trigger()
17
+ })
18
+ }
19
+ })
20
+ await page.goto('http://localhost:6006/iframe.html?globals=&args=&id=spectric-ui-components-ui-table--milti-select&viewMode=story');
21
+ });
22
+ test.describe("Spectric Table Tests", () => {
23
+
24
+ test('MultiSelect Table Select All/Deselect All ', async ({ page }) => {
25
+ let tableLocator = page.locator("spectric-table")
26
+ tableLocator.waitFor()
27
+ var selected = await tableLocator.evaluate<number, SpectricTableElement>(
28
+ async (table) => {
29
+ console.log("here")
30
+ let selectAll = table.querySelectorAll<HTMLElement>("spectric-table-header spectric-input[variant='checkbox'] spectric-button")[0]
31
+ let [event] = await window.awaitEvent<TableEvents>(table, "selected", () => selectAll.click()) as Parameters<TableEvents["selected"]>
32
+ return event.detail.length
33
+ }
34
+ )
35
+ await expect(selected, "All Selected").toBeGreaterThan(0)
36
+
37
+ selected = await tableLocator.evaluate<number, SpectricTableElement>(
38
+ async (table) => {
39
+ let selectAll = table.querySelectorAll<HTMLElement>("spectric-table-header spectric-input[variant='checkbox'] spectric-button")[0]
40
+ let [event] = await window.awaitEvent<TableEvents>(table, "selected", () => selectAll.click()) as Parameters<TableEvents["selected"]>
41
+ return event.detail.length
42
+ }
43
+ )
44
+ await expect(selected, "None Selected").toBe(0)
45
+
46
+ selected = await tableLocator.evaluate<number, SpectricTableElement>(
47
+ async (table) => {
48
+ let selectAll = table.querySelectorAll<HTMLElement>("spectric-table-virtual-body spectric-input[variant='checkbox'] spectric-button")[2]
49
+ let [event] = await window.awaitEvent<TableEvents>(table, "selected", () => selectAll.click()) as Parameters<TableEvents["selected"]>
50
+ return event.detail.length
51
+ }
52
+ )
53
+ await expect(selected, "One Selected").toBe(1)
54
+
55
+ selected = await tableLocator.evaluate<number, SpectricTableElement>(
56
+ async (table) => {
57
+ let selectAll = table.querySelectorAll<HTMLElement>("spectric-table-virtual-body spectric-input[variant='checkbox'] spectric-button")[2]
58
+ let [event] = await window.awaitEvent<TableEvents>(table, "selected", () => selectAll.click()) as Parameters<TableEvents["selected"]>
59
+ return event.detail.length
60
+ }
61
+ )
62
+ await expect(selected, "One DeSelected").toBe(0)
63
+ });
64
+
65
+
66
+ test('Ensure all event listeners are cleaned up and not leaked', async ({ page }) => {
67
+ let tableLocator = page.locator("spectric-table")
68
+ await tableLocator.waitFor()
69
+ let listenerCount = await page.evaluate(async () => {
70
+ const module = await import("./src/index.ts") as typeof SpectricModule
71
+ return module.getListeners()
72
+ })
73
+ await expect(listenerCount).toBeGreaterThan(0)
74
+ //Delete all content and expect everything to get cleaned up
75
+ listenerCount = await page.evaluate(async () => {
76
+ const module = await import("./src/index.ts") as typeof SpectricModule
77
+ document.body.innerHTML = ""
78
+ return module.getListeners()
79
+ })
80
+ await expect(listenerCount).toBe(0)
81
+
82
+ })
83
+ })
84
+
85
+
86
+
87
+ declare global {
88
+ interface Window {
89
+ awaitEvent: <EventMap extends Record<string, any>, K = keyof EventMap>(target: HTMLElement, event: K, trigger: () => void) => Parameters<EventMap[keyof EventMap]>;
90
+ }
91
+ }
@@ -3,7 +3,7 @@ import { customElement, property, } from 'lit/decorators.js';
3
3
  import { HTMLElementTagWithEvents, ReactElementWithPropsAndEvents } from '../types';
4
4
  export const TableBodyElementTag = "spectric-table-body"
5
5
  import "./cell"
6
- import { ColumnSettings, TableElement } from './table';
6
+ import { ColumnSettings, SpectricTableElement } from './table';
7
7
  import { repeat } from 'lit/directives/repeat.js';
8
8
 
9
9
  interface BodyProps<T> {
@@ -21,7 +21,7 @@ export class TableBodyElement<T> extends LitElement implements BodyProps<T> {
21
21
  data: T[] = [];
22
22
  @property({ type: Array, attribute: false })
23
23
  columns: ColumnSettings<T>[] = [];
24
- table!: TableElement<T>
24
+ table!: SpectricTableElement<T>
25
25
  protected createRenderRoot(): HTMLElement | DocumentFragment {
26
26
  return this
27
27
  }