@skewedaspect/sleekspace-ui 0.8.1 → 0.9.1

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 (191) hide show
  1. package/dist/components/Dropdown/SkDropdown.vue.d.ts +9 -1
  2. package/dist/components/Dropdown/types.d.ts +2 -1
  3. package/dist/components/NavBar/SkNavBar.vue.d.ts +9 -1
  4. package/dist/components/NavBar/context.d.ts +2 -0
  5. package/dist/components/NavBar/types.d.ts +5 -1
  6. package/dist/components/NumberInput/SkNumberInput.vue.d.ts +8 -0
  7. package/dist/components/Page/SkPage.vue.d.ts +9 -0
  8. package/dist/components/ScrollArea/SkScrollArea.vue.d.ts +105 -4
  9. package/dist/composables/useCustomColors.d.ts +18 -56
  10. package/{src → dist}/global.d.ts +6 -2
  11. package/dist/sleekspace-ui.css +4257 -1253
  12. package/dist/sleekspace-ui.es.js +300 -170
  13. package/dist/sleekspace-ui.umd.js +299 -169
  14. package/dist/static/classes.d.ts +18 -0
  15. package/dist/static/components/alert.d.ts +12 -0
  16. package/dist/static/components/avatar.d.ts +9 -0
  17. package/dist/static/components/breadcrumbs.d.ts +6 -0
  18. package/dist/static/components/button.d.ts +13 -0
  19. package/dist/static/components/card.d.ts +5 -0
  20. package/dist/static/components/checkbox.d.ts +10 -0
  21. package/dist/static/components/colorPicker.d.ts +8 -0
  22. package/dist/static/components/divider.d.ts +8 -0
  23. package/dist/static/components/dropdown.d.ts +8 -0
  24. package/dist/static/components/field.d.ts +15 -0
  25. package/dist/static/components/group.d.ts +5 -0
  26. package/dist/static/components/input.d.ts +14 -0
  27. package/dist/static/components/navBar.d.ts +16 -0
  28. package/dist/static/components/numberInput.d.ts +15 -0
  29. package/dist/static/components/page.d.ts +9 -0
  30. package/dist/static/components/pagination.d.ts +5 -0
  31. package/dist/static/components/panel.d.ts +11 -0
  32. package/dist/static/components/progress.d.ts +9 -0
  33. package/dist/static/components/radio.d.ts +11 -0
  34. package/dist/static/components/select.d.ts +10 -0
  35. package/dist/static/components/sidebar.d.ts +9 -0
  36. package/dist/static/components/skeleton.d.ts +11 -0
  37. package/dist/static/components/slider.d.ts +12 -0
  38. package/dist/static/components/spinner.d.ts +12 -0
  39. package/dist/static/components/switchInput.d.ts +10 -0
  40. package/dist/static/components/table.d.ts +12 -0
  41. package/dist/static/components/tag.d.ts +8 -0
  42. package/dist/static/components/tagsInput.d.ts +7 -0
  43. package/dist/static/components/textarea.d.ts +12 -0
  44. package/dist/static/components/toolbar.d.ts +12 -0
  45. package/dist/static/components/tooltip.d.ts +7 -0
  46. package/dist/static/escape.d.ts +2 -0
  47. package/dist/static/index.cjs.js +1 -0
  48. package/dist/static/index.d.ts +68 -0
  49. package/dist/static/index.es.js +732 -0
  50. package/dist/static/render.d.ts +12 -0
  51. package/dist/static/specs.d.ts +2 -0
  52. package/dist/static/types.d.ts +43 -0
  53. package/dist/tokens.css +322 -0
  54. package/dist/types/index.d.ts +36 -0
  55. package/dist/utils/slots.d.ts +6 -0
  56. package/docs/guides/installation.md +8 -2
  57. package/docs/guides/pure-css/_meta.yaml +8 -0
  58. package/docs/guides/pure-css/class-api.md +1070 -0
  59. package/docs/guides/pure-css/custom-elements.md +574 -0
  60. package/docs/guides/pure-css/index.md +86 -0
  61. package/docs/guides/pure-css/limitations.md +152 -0
  62. package/docs/guides/pure-css/static-helpers.md +1203 -0
  63. package/llms-full.txt +3739 -261
  64. package/package.json +19 -5
  65. package/src/components/Alert/SkAlert.vue +4 -2
  66. package/src/components/Breadcrumbs/SkBreadcrumbs.vue +6 -12
  67. package/src/components/Button/SkButton.vue +8 -5
  68. package/src/components/Card/SkCard.vue +13 -5
  69. package/src/components/Checkbox/SkCheckbox.vue +9 -2
  70. package/src/components/ContextMenu/SkContextMenuRadioGroup.vue +4 -1
  71. package/src/components/Dropdown/SkDropdown.vue +20 -3
  72. package/src/components/Dropdown/SkDropdownRadioGroup.vue +4 -1
  73. package/src/components/Dropdown/types.ts +2 -1
  74. package/src/components/Modal/SkModal.vue +11 -4
  75. package/src/components/NavBar/SkNavBar.vue +19 -8
  76. package/src/components/NavBar/context.ts +4 -2
  77. package/src/components/NavBar/types.ts +6 -1
  78. package/src/components/NumberInput/SkNumberInput.vue +10 -1
  79. package/src/components/Page/SkPage.vue +29 -15
  80. package/src/components/Panel/SkPanel.vue +2 -1
  81. package/src/components/Popover/SkPopover.vue +11 -4
  82. package/src/components/Radio/SkRadio.vue +9 -2
  83. package/src/components/ScrollArea/SkScrollArea.vue +78 -5
  84. package/src/components/Switch/SkSwitch.vue +14 -13
  85. package/src/components/Tabs/SkTab.vue +7 -2
  86. package/src/components/TreeView/SkTreeItem.vue +10 -2
  87. package/src/components/TreeView/SkTreeView.vue +7 -2
  88. package/src/composables/useCustomColors.ts +86 -77
  89. package/src/composables/usePortalContext.test.ts +0 -2
  90. package/src/shims.d.ts +10 -0
  91. package/src/static/__tests__/parity.test.ts +717 -0
  92. package/src/static/__tests__/parityHarness.test.ts +98 -0
  93. package/src/static/__tests__/parityHarness.ts +260 -0
  94. package/src/static/classes.test.ts +82 -0
  95. package/src/static/classes.ts +111 -0
  96. package/src/static/components/__tests__/helpers.test.ts +837 -0
  97. package/src/static/components/alert.ts +117 -0
  98. package/src/static/components/avatar.ts +86 -0
  99. package/src/static/components/breadcrumbs.ts +28 -0
  100. package/src/static/components/button.ts +75 -0
  101. package/src/static/components/card.ts +27 -0
  102. package/src/static/components/checkbox.ts +48 -0
  103. package/src/static/components/colorPicker.ts +45 -0
  104. package/src/static/components/divider.ts +39 -0
  105. package/src/static/components/dropdown.ts +36 -0
  106. package/src/static/components/field.ts +86 -0
  107. package/src/static/components/group.ts +27 -0
  108. package/src/static/components/input.ts +55 -0
  109. package/src/static/components/navBar.ts +94 -0
  110. package/src/static/components/numberInput.ts +64 -0
  111. package/src/static/components/page.ts +31 -0
  112. package/src/static/components/pagination.ts +27 -0
  113. package/src/static/components/panel.ts +33 -0
  114. package/src/static/components/progress.ts +31 -0
  115. package/src/static/components/radio.ts +53 -0
  116. package/src/static/components/select.ts +51 -0
  117. package/src/static/components/sidebar.ts +85 -0
  118. package/src/static/components/skeleton.ts +66 -0
  119. package/src/static/components/slider.ts +50 -0
  120. package/src/static/components/spinner.ts +94 -0
  121. package/src/static/components/switchInput.ts +49 -0
  122. package/src/static/components/table.ts +88 -0
  123. package/src/static/components/tag.ts +76 -0
  124. package/src/static/components/tagsInput.ts +35 -0
  125. package/src/static/components/textarea.ts +53 -0
  126. package/src/static/components/toolbar.ts +74 -0
  127. package/src/static/components/tooltip.ts +29 -0
  128. package/src/static/escape.test.ts +53 -0
  129. package/src/static/escape.ts +28 -0
  130. package/src/static/generated/defaults.ts +379 -0
  131. package/src/static/generated/propTypes.ts +426 -0
  132. package/src/static/index.ts +116 -0
  133. package/src/static/render.test.ts +83 -0
  134. package/src/static/render.ts +76 -0
  135. package/src/static/specs.test.ts +58 -0
  136. package/src/static/specs.ts +230 -0
  137. package/src/static/types.ts +176 -0
  138. package/src/styles/__tests__/testHelpers.ts +97 -0
  139. package/src/styles/base/_custom-elements.scss +51 -0
  140. package/src/styles/base/_index.scss +4 -0
  141. package/src/styles/components/__tests__/componentSelectors.test.ts +2575 -0
  142. package/src/styles/components/_alert.scss +82 -39
  143. package/src/styles/components/_avatar.scss +102 -47
  144. package/src/styles/components/_breadcrumbs.scss +39 -37
  145. package/src/styles/components/_button.scss +58 -5
  146. package/src/styles/components/_card.scss +64 -2
  147. package/src/styles/components/_checkbox.scss +35 -5
  148. package/src/styles/components/_color-picker.scss +48 -13
  149. package/src/styles/components/_divider.scss +86 -52
  150. package/src/styles/components/_dropdown.scss +214 -0
  151. package/src/styles/components/_field.scss +76 -23
  152. package/src/styles/components/_group.scss +190 -79
  153. package/src/styles/components/_index.scss +1 -0
  154. package/src/styles/components/_input.scss +81 -5
  155. package/src/styles/components/_menu.scss +1 -1
  156. package/src/styles/components/_navbar.scss +76 -45
  157. package/src/styles/components/_number-input.scss +98 -85
  158. package/src/styles/components/_page.scss +82 -23
  159. package/src/styles/components/_pagination.scss +240 -212
  160. package/src/styles/components/_panel.scss +268 -122
  161. package/src/styles/components/_progress.scss +120 -70
  162. package/src/styles/components/_radio.scss +35 -5
  163. package/src/styles/components/_scroll-area.scss +50 -22
  164. package/src/styles/components/_select.scss +40 -9
  165. package/src/styles/components/_sidebar.scss +59 -34
  166. package/src/styles/components/_skeleton.scss +111 -65
  167. package/src/styles/components/_slider.scss +34 -10
  168. package/src/styles/components/_spinner.scss +107 -56
  169. package/src/styles/components/_switch.scss +36 -5
  170. package/src/styles/components/_table.scss +150 -166
  171. package/src/styles/components/_tag.scss +244 -154
  172. package/src/styles/components/_tags-input.scss +46 -12
  173. package/src/styles/components/_textarea.scss +36 -5
  174. package/src/styles/components/_toolbar.scss +85 -31
  175. package/src/styles/components/_tooltip.scss +172 -3
  176. package/src/styles/mixins/_cut-border.scss +18 -4
  177. package/src/styles/mixins/_dual-selector.scss +192 -0
  178. package/src/styles/mixins/_index.scss +1 -0
  179. package/src/styles/mixins/dualSelector.test.ts +151 -0
  180. package/src/styles/themes/_colorful.scss +25 -0
  181. package/src/styles/themes/_greyscale.scss +25 -0
  182. package/src/styles/themes/_shade-scale.scss +39 -0
  183. package/src/styles/tokens/_semantic-color-kinds.scss +66 -0
  184. package/src/{types.ts → types/index.ts} +19 -11
  185. package/src/utils/slots.ts +75 -0
  186. package/web-types.json +980 -137
  187. package/dist/composables/useCustomColors.test.d.ts +0 -1
  188. package/dist/composables/useFocusTrap.test.d.ts +0 -1
  189. package/dist/composables/usePortalContext.test.d.ts +0 -1
  190. package/dist/styles/mixins/fluidSize.test.d.ts +0 -1
  191. package/dist/types.d.ts +0 -29
@@ -0,0 +1,94 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Spinner Static Helper
3
+ //
4
+ // Emits the full spinner DOM structure matching the Vue component's rendered output:
5
+ // <div class="sk-spinner sk-<kind> sk-<size> sk-variant-<variant>"
6
+ // role="status" aria-live="polite" aria-label="Loading">
7
+ // <!-- circular (default) -->
8
+ // <div class="sk-spinner-circular">
9
+ // <div class="sk-arc sk-arc-large"></div>
10
+ // <div class="sk-arc sk-arc-small"></div>
11
+ // </div>
12
+ // <!-- dots -->
13
+ // <div class="sk-spinner-dots">
14
+ // <div class="sk-dot"></div><div class="sk-dot"></div><div class="sk-dot"></div>
15
+ // </div>
16
+ // <!-- crosshair -->
17
+ // <div class="sk-crosshair-loader"></div>
18
+ // </div>
19
+ //
20
+ // Variant defaults to 'circular'. Kind defaults to 'primary'. Size defaults to 'md'.
21
+ // These must match Vue's withDefaults() so parity tests pass when explicit defaults are passed.
22
+ //----------------------------------------------------------------------------------------------------------------------
23
+
24
+ import type { ComponentKind, SpinnerSize, SpinnerVariant } from '../types';
25
+
26
+ import { escapeAttr } from '../escape';
27
+
28
+ //----------------------------------------------------------------------------------------------------------------------
29
+
30
+ export interface SpinnerStaticProps
31
+ {
32
+ kind ?: ComponentKind;
33
+ size ?: SpinnerSize;
34
+ variant ?: SpinnerVariant;
35
+
36
+ /**
37
+ * Custom color override — maps to `--sk-spinner-color` CSS variable.
38
+ * Optional; omit to use the kind-derived color.
39
+ */
40
+ color ?: string;
41
+ }
42
+
43
+ //----------------------------------------------------------------------------------------------------------------------
44
+
45
+ // Inner HTML per variant — mirrors Vue's v-if/v-else-if blocks.
46
+ function innerHtml(variant : SpinnerVariant) : string
47
+ {
48
+ switch (variant)
49
+ {
50
+ case 'circular':
51
+ return '<div class="sk-spinner-circular">'
52
+ + '<div class="sk-arc sk-arc-large"></div>'
53
+ + '<div class="sk-arc sk-arc-small"></div>'
54
+ + '</div>';
55
+
56
+ case 'dots':
57
+ return '<div class="sk-spinner-dots">'
58
+ + '<div class="sk-dot"></div>'
59
+ + '<div class="sk-dot"></div>'
60
+ + '<div class="sk-dot"></div>'
61
+ + '</div>';
62
+
63
+ case 'crosshair':
64
+ return '<div class="sk-crosshair-loader"></div>';
65
+ }
66
+ }
67
+
68
+ //----------------------------------------------------------------------------------------------------------------------
69
+
70
+ export function spinner(props : SpinnerStaticProps = {}) : string
71
+ {
72
+ const kind = props.kind ?? 'primary';
73
+ const size = props.size ?? 'md';
74
+ const variant = props.variant ?? 'circular';
75
+
76
+ const classes = [ 'sk-spinner', `sk-${ kind }`, `sk-${ size }`, `sk-variant-${ variant }` ];
77
+
78
+ const attrs : string[] = [
79
+ `class="${ escapeAttr(classes.join(' ')) }"`,
80
+ 'role="status"',
81
+ 'aria-live="polite"',
82
+ 'aria-label="Loading"',
83
+ ];
84
+
85
+ // Custom color var — emitted as inline style when `color` prop is provided.
86
+ if(typeof props.color === 'string')
87
+ {
88
+ attrs.push(`style="--sk-spinner-color: ${ escapeAttr(props.color) };"`);
89
+ }
90
+
91
+ return `<div ${ attrs.join(' ') }>${ innerHtml(variant) }</div>`;
92
+ }
93
+
94
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,49 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // SwitchInput Static Helper
3
+ //
4
+ // File is named switchInput.ts because `switch` is a reserved word in TypeScript/JavaScript.
5
+ // Emits a compound <label class="sk-switch"><input type="checkbox" name="..." />
6
+ // <span class="sk-switch-track"><span class="sk-switch-thumb"></span></span>
7
+ // <span class="sk-switch-label">children</span></label> structure for pure-CSS usage.
8
+ //----------------------------------------------------------------------------------------------------------------------
9
+
10
+ import type { ComponentKind, ComponentSize } from '../types';
11
+
12
+ import { composeClasses } from '../classes';
13
+ import { escapeAttr } from '../escape';
14
+
15
+ //----------------------------------------------------------------------------------------------------------------------
16
+
17
+ export interface SwitchStaticProps
18
+ {
19
+ kind ?: ComponentKind;
20
+ size ?: ComponentSize;
21
+ name ?: string;
22
+ checked ?: boolean;
23
+ disabled ?: boolean;
24
+ required ?: boolean;
25
+ }
26
+
27
+ //----------------------------------------------------------------------------------------------------------------------
28
+
29
+ export function switchInput(props : SwitchStaticProps = {}, children = '') : string
30
+ {
31
+ const classes = composeClasses({ base: 'sk-switch', kind: true, size: true }, props as Record<string, unknown>);
32
+
33
+ const inputAttrs : string[] = [ 'type="checkbox"' ];
34
+ if(props.name)
35
+ {
36
+ inputAttrs.push(`name="${ escapeAttr(props.name) }"`);
37
+ }
38
+ if(props.checked === true) { inputAttrs.push('checked'); }
39
+ if(props.disabled === true) { inputAttrs.push('disabled'); }
40
+ if(props.required === true) { inputAttrs.push('required'); }
41
+
42
+ const inputEl = `<input ${ inputAttrs.join(' ') } />`;
43
+ const trackEl = `<span class="sk-switch-track"><span class="sk-switch-thumb"></span></span>`;
44
+ const labelEl = `<span class="sk-switch-label">${ children }</span>`;
45
+
46
+ return `<label class="${ escapeAttr(classes) }">${ inputEl }${ trackEl }${ labelEl }</label>`;
47
+ }
48
+
49
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,88 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Table Static Helper
3
+ //
4
+ // Emits the full table DOM structure matching the Vue component's rendered output:
5
+ // <div class="sk-table-wrapper sk-table-wrapper-<kind> [sk-dark-background] [sk-subtle]">
6
+ // <table class="sk-table sk-<kind> sk-<variant> [sk-striped] [sk-hoverable] [sk-bordered]
7
+ // [sk-no-inner-borders] [sk-subtle]" [style="..."]>
8
+ // CHILDREN
9
+ // </table>
10
+ // </div>
11
+ //
12
+ // Defaults match Vue's withDefaults():
13
+ // kind='neutral', variant='default', striped=false, hoverable=true, bordered=true,
14
+ // innerBorders=false, darkBackground=false, subtle=false.
15
+ // These must be passed explicitly when using parity tests so both sides emit the same classes.
16
+ //----------------------------------------------------------------------------------------------------------------------
17
+
18
+ import type { StaticCustomColors, TableKind, TableVariant } from '../types';
19
+
20
+ import { escapeAttr } from '../escape';
21
+
22
+ //----------------------------------------------------------------------------------------------------------------------
23
+
24
+ export interface TableStaticProps extends StaticCustomColors
25
+ {
26
+ kind ?: TableKind;
27
+ variant ?: TableVariant;
28
+ striped ?: boolean;
29
+ hoverable ?: boolean;
30
+ bordered ?: boolean;
31
+ innerBorders ?: boolean;
32
+ darkBackground ?: boolean;
33
+ subtle ?: boolean;
34
+ }
35
+
36
+ //----------------------------------------------------------------------------------------------------------------------
37
+
38
+ export function table(props : TableStaticProps = {}, children = '') : string
39
+ {
40
+ const kind = props.kind ?? 'neutral';
41
+ const variant = props.variant ?? 'default';
42
+
43
+ // Prop defaults match Vue's withDefaults()
44
+ const hoverable = props.hoverable !== false; // default true
45
+ const bordered = props.bordered !== false; // default true
46
+ const innerBorders = props.innerBorders === true; // default false
47
+ const subtle = props.subtle === true;
48
+ const darkBackground = props.darkBackground === true;
49
+ const striped = props.striped === true;
50
+
51
+ // Wrapper classes (mirrors Vue's wrapperClasses)
52
+ const wrapperClasses : string[] = [ 'sk-table-wrapper', `sk-table-wrapper-${ kind }` ];
53
+ if(darkBackground) { wrapperClasses.push('sk-dark-background'); }
54
+ if(subtle) { wrapperClasses.push('sk-subtle'); }
55
+
56
+ // Table classes (mirrors Vue's tableClasses)
57
+ const tableClasses : string[] = [ 'sk-table', `sk-${ kind }`, `sk-${ variant }` ];
58
+ if(striped) { tableClasses.push('sk-striped'); }
59
+ if(hoverable) { tableClasses.push('sk-hoverable'); }
60
+ if(bordered) { tableClasses.push('sk-bordered'); }
61
+ if(!innerBorders) { tableClasses.push('sk-no-inner-borders'); }
62
+ if(subtle) { tableClasses.push('sk-subtle'); }
63
+
64
+ const tableAttrs : string[] = [ `class="${ escapeAttr(tableClasses.join(' ')) }"` ];
65
+
66
+ // Custom color vars — match Vue's useCustomColors('table', ...) output
67
+ const styleParts : string[] = [];
68
+ if(typeof props.baseColor === 'string')
69
+ {
70
+ styleParts.push(`--sk-table-color-base: ${ escapeAttr(props.baseColor) };`);
71
+ }
72
+ if(typeof props.textColor === 'string')
73
+ {
74
+ styleParts.push(`--sk-table-fg: ${ escapeAttr(props.textColor) };`);
75
+ }
76
+ if(styleParts.length > 0)
77
+ {
78
+ tableAttrs.push(`style="${ styleParts.join(' ') }"`);
79
+ }
80
+
81
+ const wrapperAttr = `class="${ escapeAttr(wrapperClasses.join(' ')) }"`;
82
+
83
+ return `<div ${ wrapperAttr }>`
84
+ + `<table ${ tableAttrs.join(' ') }>${ children }</table>`
85
+ + '</div>';
86
+ }
87
+
88
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,76 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Tag Static Helper
3
+ //
4
+ // Emits the full tag DOM structure matching the Vue component's rendered output:
5
+ // <span class="sk-tag sk-<kind> sk-<variant> sk-<size> [sk-removable]" [style="..."]>
6
+ // <span class="sk-tag-content">CHILDREN</span>
7
+ // [<button type="button" class="sk-tag-remove" aria-label="Remove">
8
+ // <svg ...>X</svg>
9
+ // </button>]
10
+ // </span>
11
+ //
12
+ // The remove button is only emitted when `removable: true`, matching Vue's v-if block.
13
+ //----------------------------------------------------------------------------------------------------------------------
14
+
15
+ import type { ComponentKind, StaticCustomColors, TagSize, TagVariant } from '../types';
16
+
17
+ import { escapeAttr } from '../escape';
18
+
19
+ //----------------------------------------------------------------------------------------------------------------------
20
+
21
+ export interface TagStaticProps extends StaticCustomColors
22
+ {
23
+ kind ?: ComponentKind;
24
+ variant ?: TagVariant;
25
+ size ?: TagSize;
26
+ removable ?: boolean;
27
+ }
28
+
29
+ //----------------------------------------------------------------------------------------------------------------------
30
+
31
+ // Remove button SVG — exact content from SkTag.vue
32
+ const REMOVE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"'
33
+ + ' viewBox="0 0 24 24" fill="none" stroke="currentColor"'
34
+ + ' stroke-width="2" stroke-linecap="round" stroke-linejoin="round">'
35
+ + '<line x1="18" y1="6" x2="6" y2="18"></line>'
36
+ + '<line x1="6" y1="6" x2="18" y2="18"></line>'
37
+ + '</svg>';
38
+
39
+ //----------------------------------------------------------------------------------------------------------------------
40
+
41
+ export function tag(props : TagStaticProps = {}, children = '') : string
42
+ {
43
+ const kind = props.kind ?? 'neutral';
44
+ const variant = props.variant ?? 'solid';
45
+ const size = props.size ?? 'md';
46
+
47
+ const classes : string[] = [ 'sk-tag', `sk-${ kind }`, `sk-${ variant }`, `sk-${ size }` ];
48
+ if(props.removable) { classes.push('sk-removable'); }
49
+
50
+ const attrs : string[] = [ `class="${ escapeAttr(classes.join(' ')) }"` ];
51
+
52
+ // Custom color vars — match Vue's useCustomColors('tag', ...) output
53
+ const styleParts : string[] = [];
54
+ if(typeof props.baseColor === 'string')
55
+ {
56
+ styleParts.push(`--sk-tag-color-base: ${ escapeAttr(props.baseColor) };`);
57
+ }
58
+ if(typeof props.textColor === 'string')
59
+ {
60
+ styleParts.push(`--sk-tag-fg: ${ escapeAttr(props.textColor) };`);
61
+ }
62
+ if(styleParts.length > 0)
63
+ {
64
+ attrs.push(`style="${ styleParts.join(' ') }"`);
65
+ }
66
+
67
+ const contentSpan = `<span class="sk-tag-content">${ children }</span>`;
68
+
69
+ const removeButton = props.removable
70
+ ? `<button type="button" class="sk-tag-remove" aria-label="Remove">${ REMOVE_SVG }</button>`
71
+ : '';
72
+
73
+ return `<span ${ attrs.join(' ') }>${ contentSpan }${ removeButton }</span>`;
74
+ }
75
+
76
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,35 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // TagsInput Static Helper
3
+ //
4
+ // Emits a <div class="sk-tags-input">children</div> container. Children are hand-composed
5
+ // tag chip elements by the caller — the dynamic add/remove behavior requires Vue/JS and
6
+ // cannot be replicated in a static helper.
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+
9
+ import type { ComponentKind, ComponentSize } from '../types';
10
+
11
+ import { composeClasses } from '../classes';
12
+ import { escapeAttr } from '../escape';
13
+
14
+ //----------------------------------------------------------------------------------------------------------------------
15
+
16
+ export interface TagsInputStaticProps
17
+ {
18
+ kind ?: ComponentKind;
19
+ size ?: ComponentSize;
20
+ disabled ?: boolean;
21
+ }
22
+
23
+ //----------------------------------------------------------------------------------------------------------------------
24
+
25
+ export function tagsInput(props : TagsInputStaticProps = {}, children = '') : string
26
+ {
27
+ const classes = composeClasses(
28
+ { base: 'sk-tags-input', kind: true, size: true },
29
+ props as Record<string, unknown>
30
+ );
31
+
32
+ return `<div class="${ escapeAttr(classes) }">${ children }</div>`;
33
+ }
34
+
35
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,53 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Textarea Static Helper
3
+ //
4
+ // Emits a non-void <textarea class="sk-textarea">children</textarea> with passthrough attrs for
5
+ // value, placeholder, name, id, and boolean attrs (disabled, readonly, required).
6
+ //----------------------------------------------------------------------------------------------------------------------
7
+
8
+ import type { ComponentKind, ComponentSize } from '../types';
9
+
10
+ import { composeClasses } from '../classes';
11
+ import { escapeAttr } from '../escape';
12
+
13
+ //----------------------------------------------------------------------------------------------------------------------
14
+
15
+ export interface TextareaStaticProps
16
+ {
17
+ kind ?: ComponentKind;
18
+ size ?: ComponentSize;
19
+ placeholder ?: string;
20
+ name ?: string;
21
+ id ?: string;
22
+ disabled ?: boolean;
23
+ readonly ?: boolean;
24
+ required ?: boolean;
25
+ }
26
+
27
+ //----------------------------------------------------------------------------------------------------------------------
28
+
29
+ export function textarea(props : TextareaStaticProps = {}, children = '') : string
30
+ {
31
+ const classes = composeClasses({ base: 'sk-textarea', kind: true, size: true }, props as Record<string, unknown>);
32
+ const attrs : string[] = [ `class="${ escapeAttr(classes) }"` ];
33
+
34
+ const passthroughString = [ 'placeholder', 'name', 'id' ] as const;
35
+ for(const key of passthroughString)
36
+ {
37
+ const val = props[key];
38
+ if(typeof val === 'string')
39
+ {
40
+ attrs.push(`${ key }="${ escapeAttr(val) }"`);
41
+ }
42
+ }
43
+
44
+ const passthroughBool = [ 'disabled', 'readonly', 'required' ] as const;
45
+ for(const key of passthroughBool)
46
+ {
47
+ if(props[key] === true) { attrs.push(key); }
48
+ }
49
+
50
+ return `<textarea ${ attrs.join(' ') }>${ children }</textarea>`;
51
+ }
52
+
53
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,74 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Toolbar Static Helper
3
+ //
4
+ // Emits a class-API toolbar element matching the Vue component's class structure. The Vue
5
+ // component uses reka-ui ToolbarRoot, which injects runtime attributes (aria-orientation,
6
+ // data-orientation, dir, tabindex, outline style) that have no static equivalent. The parity
7
+ // harness filters these reka-ui-injected attributes — only the class structure is compared.
8
+ //
9
+ // Corners default to all four (matching Vue's `corners: () => ['top-left', 'top-right',
10
+ // 'bottom-right', 'bottom-left']` default) so the sk-cut-* classes are emitted by default.
11
+ //
12
+ // Orientation: Vue emits sk-horizontal / sk-vertical directly (not sk-orientation-horizontal),
13
+ // so the class is composed manually here rather than using the singleChoiceFlags mechanism.
14
+ //----------------------------------------------------------------------------------------------------------------------
15
+
16
+ import type { StaticCustomColors, ToolbarCorner, ToolbarKind, ToolbarOrientation } from '../types';
17
+
18
+ import { escapeAttr } from '../escape';
19
+
20
+ //----------------------------------------------------------------------------------------------------------------------
21
+
22
+ export interface ToolbarStaticProps extends StaticCustomColors
23
+ {
24
+ kind ?: ToolbarKind;
25
+ orientation ?: ToolbarOrientation;
26
+
27
+ /**
28
+ * Corner cuts. Defaults to all four corners, matching the Vue component's default.
29
+ * Pass an empty array to suppress all cuts.
30
+ * @default ['top-left', 'top-right', 'bottom-right', 'bottom-left']
31
+ */
32
+ corners ?: ToolbarCorner[];
33
+ }
34
+
35
+ //----------------------------------------------------------------------------------------------------------------------
36
+
37
+ const DEFAULT_CORNERS : ToolbarCorner[] = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left' ];
38
+
39
+ //----------------------------------------------------------------------------------------------------------------------
40
+
41
+ export function toolbar(props : ToolbarStaticProps = {}, children = '') : string
42
+ {
43
+ const kind = props.kind;
44
+ const orientation = props.orientation ?? 'horizontal';
45
+ const corners = props.corners ?? DEFAULT_CORNERS;
46
+
47
+ // Build class list in the same order as Vue's computed classes object:
48
+ // sk-toolbar → sk-<kind> → sk-<orientation> → sk-cut-*
49
+ const parts : string[] = [ 'sk-toolbar' ];
50
+ if(typeof kind === 'string') { parts.push(`sk-${ kind }`); }
51
+ parts.push(`sk-${ orientation }`);
52
+ for(const corner of corners) { parts.push(`sk-cut-${ corner }`); }
53
+
54
+ const attrs : string[] = [ `class="${ escapeAttr(parts.join(' ')) }"`, 'role="toolbar"' ];
55
+
56
+ // Custom color vars
57
+ const styleParts : string[] = [];
58
+ if(typeof props.baseColor === 'string')
59
+ {
60
+ styleParts.push(`--sk-toolbar-color-base: ${ escapeAttr(props.baseColor) };`);
61
+ }
62
+ if(typeof props.textColor === 'string')
63
+ {
64
+ styleParts.push(`--sk-toolbar-fg: ${ escapeAttr(props.textColor) };`);
65
+ }
66
+ if(styleParts.length > 0)
67
+ {
68
+ attrs.push(`style="${ styleParts.join(' ') }"`);
69
+ }
70
+
71
+ return `<div ${ attrs.join(' ') }>${ children }</div>`;
72
+ }
73
+
74
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,29 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // Tooltip Static Helper
3
+ //
4
+ // Thin typed wrapper over the generic render() core. Emits a class-API <div role="tooltip">
5
+ // with sk-tooltip modifier classes derived from props.
6
+ //----------------------------------------------------------------------------------------------------------------------
7
+
8
+ import type { StaticCustomColors, TooltipKind, TooltipSide, TooltipVariant } from '../types';
9
+
10
+ import { render } from '../render';
11
+ import { SPECS } from '../specs';
12
+
13
+ //----------------------------------------------------------------------------------------------------------------------
14
+
15
+ export interface TooltipStaticProps extends StaticCustomColors
16
+ {
17
+ kind ?: TooltipKind;
18
+ variant ?: TooltipVariant;
19
+ placement ?: TooltipSide;
20
+ }
21
+
22
+ //----------------------------------------------------------------------------------------------------------------------
23
+
24
+ export function tooltip(props : TooltipStaticProps = {}, children = '') : string
25
+ {
26
+ return render(SPECS.tooltip, props as Record<string, unknown>, children);
27
+ }
28
+
29
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,53 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // HTML Escape Tests
3
+ //----------------------------------------------------------------------------------------------------------------------
4
+
5
+ import { describe, expect, it } from 'vitest';
6
+
7
+ import { escapeAttr, text } from './escape';
8
+
9
+ //----------------------------------------------------------------------------------------------------------------------
10
+
11
+ describe('escapeAttr', () =>
12
+ {
13
+ it('escapes double quotes', () =>
14
+ {
15
+ expect(escapeAttr('say "hi"')).toBe('say &quot;hi&quot;');
16
+ });
17
+
18
+ it('escapes ampersands', () =>
19
+ {
20
+ expect(escapeAttr('a & b')).toBe('a &amp; b');
21
+ });
22
+
23
+ it('escapes less-than and greater-than', () =>
24
+ {
25
+ expect(escapeAttr('<x>')).toBe('&lt;x&gt;');
26
+ });
27
+
28
+ it('handles empty string', () =>
29
+ {
30
+ expect(escapeAttr('')).toBe('');
31
+ });
32
+
33
+ it('passes plain text through', () =>
34
+ {
35
+ expect(escapeAttr('hello world')).toBe('hello world');
36
+ });
37
+ });
38
+
39
+ describe('text', () =>
40
+ {
41
+ it('escapes all entity characters', () =>
42
+ {
43
+ expect(text('<div class="x" & y>'))
44
+ .toBe('&lt;div class=&quot;x&quot; &amp; y&gt;');
45
+ });
46
+
47
+ it('escapes single quotes', () =>
48
+ {
49
+ expect(text("it's")).toBe('it&#39;s');
50
+ });
51
+ });
52
+
53
+ //----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,28 @@
1
+ //----------------------------------------------------------------------------------------------------------------------
2
+ // HTML Escape Utilities
3
+ //
4
+ // `escapeAttr` escapes values destined for HTML attribute context: &, <, >, and double quotes.
5
+ // `text` escapes values destined for HTML text content — same set plus single quotes for
6
+ // defensive double-escaping when interpolated into single-quoted attributes.
7
+ //----------------------------------------------------------------------------------------------------------------------
8
+
9
+ export function escapeAttr(value : string) : string
10
+ {
11
+ return value
12
+ .replace(/&/g, '&amp;')
13
+ .replace(/</g, '&lt;')
14
+ .replace(/>/g, '&gt;')
15
+ .replace(/"/g, '&quot;');
16
+ }
17
+
18
+ export function text(value : string) : string
19
+ {
20
+ return value
21
+ .replace(/&/g, '&amp;')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;')
24
+ .replace(/"/g, '&quot;')
25
+ .replace(/'/g, '&#39;');
26
+ }
27
+
28
+ //----------------------------------------------------------------------------------------------------------------------