frutjam 2.7.1-beta.1 → 3.0.0

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 (207) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +117 -19
  3. package/dist/base/reboot/index.js +5 -0
  4. package/dist/base/reboot/object.js +1 -0
  5. package/dist/base/tokens/index.js +5 -0
  6. package/dist/base/tokens/object.js +1 -0
  7. package/dist/base.css +4 -0
  8. package/dist/components/accordion/index.js +7 -0
  9. package/dist/components/accordion/object.js +1 -0
  10. package/dist/components/accordion.css +2 -0
  11. package/dist/components/alert/index.js +7 -0
  12. package/dist/components/alert/object.js +1 -0
  13. package/dist/components/alert.css +2 -0
  14. package/dist/components/avatar/index.js +7 -0
  15. package/dist/components/avatar/object.js +1 -0
  16. package/dist/components/avatar.css +2 -0
  17. package/dist/components/badge/index.js +7 -0
  18. package/dist/components/badge/object.js +1 -0
  19. package/dist/components/badge.css +2 -0
  20. package/dist/components/breadcrumb/index.js +7 -0
  21. package/dist/components/breadcrumb/object.js +1 -0
  22. package/dist/components/breadcrumb.css +2 -0
  23. package/dist/components/button/index.js +7 -0
  24. package/dist/components/button/object.js +1 -0
  25. package/dist/components/button.css +4 -0
  26. package/dist/components/card/index.js +7 -0
  27. package/dist/components/card/object.js +1 -0
  28. package/dist/components/card.css +2 -0
  29. package/dist/components/carousel/index.js +7 -0
  30. package/dist/components/carousel/object.js +1 -0
  31. package/dist/components/carousel.css +2 -0
  32. package/dist/components/chat/index.js +7 -0
  33. package/dist/components/chat/object.js +1 -0
  34. package/dist/components/chat.css +2 -0
  35. package/dist/components/checkbox/index.js +7 -0
  36. package/dist/components/checkbox/object.js +1 -0
  37. package/dist/components/checkbox.css +2 -0
  38. package/dist/components/collapsible/index.js +7 -0
  39. package/dist/components/collapsible/object.js +1 -0
  40. package/dist/components/collapsible.css +2 -0
  41. package/dist/components/combobox/index.js +7 -0
  42. package/dist/components/combobox/object.js +1 -0
  43. package/dist/components/combobox.css +2 -0
  44. package/dist/components/countdown/index.js +7 -0
  45. package/dist/components/countdown/object.js +1 -0
  46. package/dist/components/countdown.css +2 -0
  47. package/dist/components/diff/index.js +7 -0
  48. package/dist/components/diff/object.js +1 -0
  49. package/dist/components/diff.css +2 -0
  50. package/dist/components/divider/index.js +7 -0
  51. package/dist/components/divider/object.js +1 -0
  52. package/dist/components/divider.css +2 -0
  53. package/dist/components/drawer/index.js +7 -0
  54. package/dist/components/drawer/object.js +1 -0
  55. package/dist/components/drawer.css +2 -0
  56. package/dist/components/footer/index.js +7 -0
  57. package/dist/components/footer/object.js +1 -0
  58. package/dist/components/footer.css +2 -0
  59. package/dist/components/header/index.js +7 -0
  60. package/dist/components/header/object.js +1 -0
  61. package/dist/components/header.css +2 -0
  62. package/dist/components/hero/index.js +7 -0
  63. package/dist/components/hero/object.js +1 -0
  64. package/dist/components/hero.css +2 -0
  65. package/dist/components/indicator/index.js +7 -0
  66. package/dist/components/indicator/object.js +1 -0
  67. package/dist/components/indicator.css +2 -0
  68. package/dist/components/input/index.js +7 -0
  69. package/dist/components/input/object.js +1 -0
  70. package/dist/components/input.css +2 -0
  71. package/dist/components/join/index.js +7 -0
  72. package/dist/components/join/object.js +1 -0
  73. package/dist/components/join.css +2 -0
  74. package/dist/components/kbd/index.js +7 -0
  75. package/dist/components/kbd/object.js +1 -0
  76. package/dist/components/kbd.css +2 -0
  77. package/dist/components/link/index.js +7 -0
  78. package/dist/components/link/object.js +1 -0
  79. package/dist/components/link.css +2 -0
  80. package/dist/components/loading/index.js +7 -0
  81. package/dist/components/loading/object.js +1 -0
  82. package/dist/components/loading.css +2 -0
  83. package/dist/components/mask/index.js +7 -0
  84. package/dist/components/mask/object.js +1 -0
  85. package/dist/components/mask.css +2 -0
  86. package/dist/components/menu/index.js +7 -0
  87. package/dist/components/menu/object.js +1 -0
  88. package/dist/components/menu.css +2 -0
  89. package/dist/components/modal/index.js +7 -0
  90. package/dist/components/modal/object.js +1 -0
  91. package/dist/components/modal.css +2 -0
  92. package/dist/components/navbar/index.js +7 -0
  93. package/dist/components/navbar/object.js +1 -0
  94. package/dist/components/navbar.css +2 -0
  95. package/dist/components/popover/index.js +7 -0
  96. package/dist/components/popover/object.js +1 -0
  97. package/dist/components/popover.css +2 -0
  98. package/dist/components/progress/index.js +7 -0
  99. package/dist/components/progress/object.js +1 -0
  100. package/dist/components/progress.css +2 -0
  101. package/dist/components/radial-progress/index.js +7 -0
  102. package/dist/components/radial-progress/object.js +1 -0
  103. package/dist/components/radial-progress.css +2 -0
  104. package/dist/components/radio/index.js +7 -0
  105. package/dist/components/radio/object.js +1 -0
  106. package/dist/components/radio.css +2 -0
  107. package/dist/components/range/index.js +7 -0
  108. package/dist/components/range/object.js +1 -0
  109. package/dist/components/range.css +2 -0
  110. package/dist/components/rating/index.js +7 -0
  111. package/dist/components/rating/object.js +1 -0
  112. package/dist/components/rating.css +2 -0
  113. package/dist/components/select/index.js +7 -0
  114. package/dist/components/select/object.js +1 -0
  115. package/dist/components/select.css +2 -0
  116. package/dist/components/sidebar/index.js +7 -0
  117. package/dist/components/sidebar/object.js +1 -0
  118. package/dist/components/sidebar.css +2 -0
  119. package/dist/components/skeleton/index.js +7 -0
  120. package/dist/components/skeleton/object.js +1 -0
  121. package/dist/components/skeleton.css +2 -0
  122. package/dist/components/stat/index.js +7 -0
  123. package/dist/components/stat/object.js +1 -0
  124. package/dist/components/stat.css +2 -0
  125. package/dist/components/status/index.js +7 -0
  126. package/dist/components/status/object.js +1 -0
  127. package/dist/components/status.css +2 -0
  128. package/dist/components/steps/index.js +7 -0
  129. package/dist/components/steps/object.js +1 -0
  130. package/dist/components/steps.css +2 -0
  131. package/dist/components/surface/index.js +7 -0
  132. package/dist/components/surface/object.js +1 -0
  133. package/dist/components/surface.css +2 -0
  134. package/dist/components/swap/index.js +7 -0
  135. package/dist/components/swap/object.js +1 -0
  136. package/dist/components/swap.css +2 -0
  137. package/dist/components/table/index.js +7 -0
  138. package/dist/components/table/object.js +1 -0
  139. package/dist/components/table.css +2 -0
  140. package/dist/components/tabs/index.js +7 -0
  141. package/dist/components/tabs/object.js +1 -0
  142. package/dist/components/tabs.css +2 -0
  143. package/dist/components/tag/index.js +7 -0
  144. package/dist/components/tag/object.js +1 -0
  145. package/dist/components/tag.css +2 -0
  146. package/dist/components/textarea/index.js +7 -0
  147. package/dist/components/textarea/object.js +1 -0
  148. package/dist/components/textarea.css +2 -0
  149. package/dist/components/timeline/index.js +7 -0
  150. package/dist/components/timeline/object.js +1 -0
  151. package/dist/components/timeline.css +2 -0
  152. package/dist/components/toast/index.js +7 -0
  153. package/dist/components/toast/object.js +1 -0
  154. package/dist/components/toast.css +2 -0
  155. package/dist/components/toggle/index.js +7 -0
  156. package/dist/components/toggle/object.js +1 -0
  157. package/dist/components/toggle.css +2 -0
  158. package/dist/components/tooltip/index.js +7 -0
  159. package/dist/components/tooltip/object.js +1 -0
  160. package/dist/components/tooltip.css +2 -0
  161. package/dist/frutjam.min.css +6 -5
  162. package/dist/functions/addPrefix.js +14 -0
  163. package/dist/plugin.js +146 -0
  164. package/dist/themes/darkberry/index.js +5 -0
  165. package/dist/themes/darkberry/object.js +1 -0
  166. package/dist/themes/darkberry.css +2 -0
  167. package/dist/themes/snowberry/index.js +5 -0
  168. package/dist/themes/snowberry/object.js +1 -0
  169. package/dist/themes/snowberry.css +2 -0
  170. package/dist/utilities/headings/index.js +7 -0
  171. package/dist/utilities/headings/object.js +1 -0
  172. package/dist/utilities/headings.css +2 -0
  173. package/dist/utilities/para/index.js +7 -0
  174. package/dist/utilities/para/object.js +1 -0
  175. package/dist/utilities/para.css +2 -0
  176. package/dist/utilities/typography/index.js +7 -0
  177. package/dist/utilities/typography/object.js +1 -0
  178. package/dist/utilities/typography.css +2 -0
  179. package/dist/utilities/wrapper/index.js +7 -0
  180. package/dist/utilities/wrapper/object.js +1 -0
  181. package/dist/utilities/wrapper.css +2 -0
  182. package/index.js +272 -0
  183. package/package.json +51 -40
  184. package/packages/js/carousel.js +19 -0
  185. package/packages/js/collapsible.js +8 -0
  186. package/packages/js/combobox.js +79 -0
  187. package/packages/js/drawer.js +1 -0
  188. package/packages/js/index.js +8 -0
  189. package/packages/js/modal.js +8 -0
  190. package/packages/js/tabs.js +33 -0
  191. package/packages/js/toast.js +19 -0
  192. package/packages/js/tooltip.js +8 -0
  193. package/packages/react/index.js +8 -0
  194. package/packages/react/useCarousel.js +23 -0
  195. package/packages/react/useCollapsible.js +12 -0
  196. package/packages/react/useCombobox.js +69 -0
  197. package/packages/react/useDrawer.js +1 -0
  198. package/packages/react/useModal.js +12 -0
  199. package/packages/react/useTabs.js +23 -0
  200. package/packages/react/useToast.js +27 -0
  201. package/packages/react/useTooltip.js +12 -0
  202. package/dist/frutjam.local.css +0 -2
  203. package/dist/frutjam.min.css.map +0 -1
  204. package/dist/frutjam.min.js +0 -3
  205. package/dist/frutjam.min.js.LICENSE.txt +0 -1
  206. package/dist/frutjam.min.js.map +0 -1
  207. package/dist/frutjam.safelist.local.css +0 -1
@@ -0,0 +1,5 @@
1
+ import theme from './object.js';
2
+
3
+ export default ({ addBase }) => {
4
+ addBase({ ...theme });
5
+ };
@@ -0,0 +1 @@
1
+ export default {"@layer theme":{":is([data-theme=\"darkberry\"], [data-theme=\"dark\"])":{"--scheme-color":"dark","--border-radius":"0.25rem","--color-base":"oklch(10.889% 0.00301 17.398)","--color-on-base":"oklch(98% 0.01 270)","--color-neutral":"oklch(85% 0 0)","--color-on-neutral":"oklch(20% 0 0)","--color-primary":"oklch(66.385% 0.18368 285.592)","--color-on-primary":"oklch(10% 0.01 270)","--color-secondary":"oklch(65% 0.18 220)","--color-on-secondary":"oklch(10% 0.01 220)","--color-accent":"oklch(75% 0.18 50)","--color-on-accent":"oklch(10% 0.01 50)","--color-info":"oklch(91.67% .077 205.04)","--color-on-info":"oklch(10% 0.01 250)","--color-success":"oklch(65% 0.18 140)","--color-on-success":"oklch(10% 0.01 140)","--color-warning":"oklch(75% 0.20 85)","--color-on-warning":"oklch(10% 0.01 85)","--color-error":"oklch(60% 0.22 30)","--color-on-error":"oklch(10% 0.01 30)"}}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ @layer theme{:is([data-theme=darkberry],[data-theme=dark]){--scheme-color:dark;--border-radius:0.25rem;--color-base:oklch(10.889% 0.00301 17.398);--color-on-base:oklch(98% 0.01 270);--color-neutral:oklch(85% 0 0);--color-on-neutral:oklch(20% 0 0);--color-primary:oklch(66.385% 0.18368 285.592);--color-on-primary:oklch(10% 0.01 270);--color-secondary:oklch(65% 0.18 220);--color-on-secondary:oklch(10% 0.01 220);--color-accent:oklch(75% 0.18 50);--color-on-accent:oklch(10% 0.01 50);--color-info:oklch(91.67% .077 205.04);--color-on-info:oklch(10% 0.01 250);--color-success:oklch(65% 0.18 140);--color-on-success:oklch(10% 0.01 140);--color-warning:oklch(75% 0.20 85);--color-on-warning:oklch(10% 0.01 85);--color-error:oklch(60% 0.22 30);--color-on-error:oklch(10% 0.01 30)}}
@@ -0,0 +1,5 @@
1
+ import theme from './object.js';
2
+
3
+ export default ({ addBase }) => {
4
+ addBase({ ...theme });
5
+ };
@@ -0,0 +1 @@
1
+ export default {"@layer theme":{":root:not([data-theme]), :is([data-theme=\"snowberry\"], [data-theme=\"light\"])":{"--scheme-color":"light","--border-radius":"0.25rem","--color-base":"oklch(1 0 0)","--color-on-base":"oklch(0.22 0 0)","--color-neutral":"oklch(0.15 0 0)","--color-on-neutral":"oklch(1 0 0)","--color-primary":"oklch(51.1% 0.262 276.966)","--color-on-primary":"oklch(96.2% 0.018 272.314)","--color-secondary":"oklch(0.591 0.293 322.896)","--color-on-secondary":"oklch(1 0 0)","--color-accent":"oklch(0.541 0.281 293.009)","--color-on-accent":"oklch(1 0 0)","--color-info":"oklch(0.685 0.169 237.323)","--color-on-info":"oklch(0.15 0 0)","--color-success":"oklch(0.792 0.209 151.711)","--color-on-success":"oklch(0.15 0 0)","--color-warning":"oklch(0.852 0.199 91.936)","--color-on-warning":"oklch(0.15 0 0)","--color-error":"oklch(0.577 0.245 27.325)","--color-on-error":"oklch(1 0 0)"}}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ @layer theme{:is([data-theme=snowberry],[data-theme=light]),:root:not([data-theme]){--scheme-color:light;--border-radius:0.25rem;--color-base:oklch(1 0 0);--color-on-base:oklch(0.22 0 0);--color-neutral:oklch(0.15 0 0);--color-on-neutral:oklch(1 0 0);--color-primary:oklch(51.1% 0.262 276.966);--color-on-primary:oklch(96.2% 0.018 272.314);--color-secondary:oklch(0.591 0.293 322.896);--color-on-secondary:oklch(1 0 0);--color-accent:oklch(0.541 0.281 293.009);--color-on-accent:oklch(1 0 0);--color-info:oklch(0.685 0.169 237.323);--color-on-info:oklch(0.15 0 0);--color-success:oklch(0.792 0.209 151.711);--color-on-success:oklch(0.15 0 0);--color-warning:oklch(0.852 0.199 91.936);--color-on-warning:oklch(0.15 0 0);--color-error:oklch(0.577 0.245 27.325);--color-on-error:oklch(1 0 0)}}
@@ -0,0 +1,7 @@
1
+ import headings from './object.js';
2
+ import { addPrefix } from '../../functions/addPrefix.js';
3
+
4
+ export default ({ addUtilities, prefix = '' }) => {
5
+ const prefixed = addPrefix(headings, prefix);
6
+ addUtilities({ ...prefixed });
7
+ };
@@ -0,0 +1 @@
1
+ export default {".heading-2xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-2xl)","line-height":1.15},".heading-3xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-3xl)","line-height":1.15},".heading-4xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-4xl)","line-height":1.15},".heading-5xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-5xl)","line-height":1.15},".heading-6xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-6xl)","line-height":1.15},".heading-7xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-7xl)","line-height":1.15},".heading-8xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-8xl)","line-height":1.15},".heading-9xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-9xl)","line-height":1.15},".heading-lg":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-lg)","line-height":1.15},".heading-md":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","line-height":1.15,"font-size":"var(--text-base)"},".heading-sm":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-sm)","line-height":1.15},".heading-xl":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-xl)","line-height":1.15},".heading-xs":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-xs)","line-height":1.15},".heading":{"color":"var(--color-on-base)","font-weight":"bold","text-wrap-style":"balance","font-size":"var(--text-base)","line-height":1.15}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */.heading-2xl{font-size:var(--text-base);font-size:var(--text-2xl)}.heading-2xl,.heading-3xl{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-3xl{font-size:var(--text-base);font-size:var(--text-3xl)}.heading-4xl{font-size:var(--text-base);font-size:var(--text-4xl)}.heading-4xl,.heading-5xl{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-5xl{font-size:var(--text-base);font-size:var(--text-5xl)}.heading-6xl{font-size:var(--text-base);font-size:var(--text-6xl)}.heading-6xl,.heading-7xl{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-7xl{font-size:var(--text-base);font-size:var(--text-7xl)}.heading-8xl{font-size:var(--text-base);font-size:var(--text-8xl)}.heading-8xl,.heading-9xl{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-9xl{font-size:var(--text-base);font-size:var(--text-9xl)}.heading-lg{font-size:var(--text-base);font-size:var(--text-lg)}.heading-lg,.heading-md{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-md{font-size:var(--text-base)}.heading-sm{font-size:var(--text-base);font-size:var(--text-sm)}.heading-sm,.heading-xl{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading-xl{font-size:var(--text-base);font-size:var(--text-xl)}.heading-xs{font-size:var(--text-base);font-size:var(--text-xs)}.heading,.heading-xs{color:var(--color-on-base);font-weight:700;line-height:1.15;text-wrap-style:balance}.heading{font-size:var(--text-base)}
@@ -0,0 +1,7 @@
1
+ import para from './object.js';
2
+ import { addPrefix } from '../../functions/addPrefix.js';
3
+
4
+ export default ({ addUtilities, prefix = '' }) => {
5
+ const prefixed = addPrefix(para, prefix);
6
+ addUtilities({ ...prefixed });
7
+ };
@@ -0,0 +1 @@
1
+ export default {".para-2xl":{"text-wrap-style":"pretty","font-size":"var(--text-2xl)","line-height":1.6},".para-3xl":{"text-wrap-style":"pretty","font-size":"var(--text-3xl)","line-height":1.6},".para-4xl":{"text-wrap-style":"pretty","font-size":"var(--text-4xl)","line-height":1.6},".para-5xl":{"text-wrap-style":"pretty","font-size":"var(--text-5xl)","line-height":1.6},".para-6xl":{"text-wrap-style":"pretty","font-size":"var(--text-6xl)","line-height":1.6},".para-7xl":{"text-wrap-style":"pretty","font-size":"var(--text-7xl)","line-height":1.6},".para-8xl":{"text-wrap-style":"pretty","font-size":"var(--text-8xl)","line-height":1.6},".para-9xl":{"text-wrap-style":"pretty","font-size":"var(--text-9xl)","line-height":1.6},".para-lg":{"text-wrap-style":"pretty","font-size":"var(--text-lg)","line-height":1.6},".para-md":{"text-wrap-style":"pretty","line-height":1.6,"font-size":"var(--text-base)"},".para-sm":{"text-wrap-style":"pretty","font-size":"var(--text-sm)","line-height":1.6},".para-xl":{"text-wrap-style":"pretty","font-size":"var(--text-xl)","line-height":1.6},".para-xs":{"text-wrap-style":"pretty","font-size":"var(--text-xs)","line-height":1.6},".para":{"text-wrap-style":"pretty","font-size":"var(--text-base)","line-height":1.6}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */.para-2xl{font-size:var(--text-base);font-size:var(--text-2xl)}.para-2xl,.para-3xl{line-height:1.6;text-wrap-style:pretty}.para-3xl{font-size:var(--text-base);font-size:var(--text-3xl)}.para-4xl{font-size:var(--text-base);font-size:var(--text-4xl)}.para-4xl,.para-5xl{line-height:1.6;text-wrap-style:pretty}.para-5xl{font-size:var(--text-base);font-size:var(--text-5xl)}.para-6xl{font-size:var(--text-base);font-size:var(--text-6xl)}.para-6xl,.para-7xl{line-height:1.6;text-wrap-style:pretty}.para-7xl{font-size:var(--text-base);font-size:var(--text-7xl)}.para-8xl{font-size:var(--text-base);font-size:var(--text-8xl)}.para-8xl,.para-9xl{line-height:1.6;text-wrap-style:pretty}.para-9xl{font-size:var(--text-base);font-size:var(--text-9xl)}.para-lg{font-size:var(--text-base);font-size:var(--text-lg)}.para-lg,.para-md{line-height:1.6;text-wrap-style:pretty}.para-md{font-size:var(--text-base)}.para-sm{font-size:var(--text-base);font-size:var(--text-sm)}.para-sm,.para-xl{line-height:1.6;text-wrap-style:pretty}.para-xl{font-size:var(--text-base);font-size:var(--text-xl)}.para-xs{font-size:var(--text-base);font-size:var(--text-xs)}.para,.para-xs{line-height:1.6;text-wrap-style:pretty}.para{font-size:var(--text-base)}
@@ -0,0 +1,7 @@
1
+ import typography from './object.js';
2
+ import { addPrefix } from '../../functions/addPrefix.js';
3
+
4
+ export default ({ addUtilities, prefix = '' }) => {
5
+ const prefixed = addPrefix(typography, prefix);
6
+ addUtilities({ ...prefixed });
7
+ };
@@ -0,0 +1 @@
1
+ export default {".prose-frutjam":{"--tw-prose-body":"var(--color-body)","--tw-prose-headings":"var(--color-on-base)","--tw-prose-lead":"var(--color-on-neutral)","--tw-prose-bold":"var(--color-on-base)","--tw-prose-links":"var(--color-primary)","--tw-prose-counters":"var(--color-on-neutral)","--tw-prose-bullets":"var(--color-neutral)","--tw-prose-hr":"var(--color-base-soft)","--tw-prose-quotes":"var(--color-on-base)","--tw-prose-quote-borders":"var(--color-primary)","--tw-prose-code":"var(--color-on-base)","--tw-prose-pre-code":"var(--color-base)","--tw-prose-pre-bg":"var(--color-on-base)","--tw-prose-th-borders":"var(--color-base-soft)","--tw-prose-td-borders":"var(--color-base-soft)","--tw-prose-captions":"var(--color-on-neutral)","--tw-prose-kbd":"var(--color-body)","--tw-prose-invert-body":"var(--color-on-neutral)","--tw-prose-invert-headings":"var(--color-on-base)","--tw-prose-invert-links":"var(--color-primary)"}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ .prose-frutjam{--tw-prose-body:var(--color-body);--tw-prose-headings:var(--color-on-base);--tw-prose-lead:var(--color-on-neutral);--tw-prose-bold:var(--color-on-base);--tw-prose-links:var(--color-primary);--tw-prose-counters:var(--color-on-neutral);--tw-prose-bullets:var(--color-neutral);--tw-prose-hr:var(--color-base-soft);--tw-prose-quotes:var(--color-on-base);--tw-prose-quote-borders:var(--color-primary);--tw-prose-code:var(--color-on-base);--tw-prose-pre-code:var(--color-base);--tw-prose-pre-bg:var(--color-on-base);--tw-prose-th-borders:var(--color-base-soft);--tw-prose-td-borders:var(--color-base-soft);--tw-prose-captions:var(--color-on-neutral);--tw-prose-kbd:var(--color-body);--tw-prose-invert-body:var(--color-on-neutral);--tw-prose-invert-headings:var(--color-on-base);--tw-prose-invert-links:var(--color-primary)}
@@ -0,0 +1,7 @@
1
+ import wrapper from './object.js';
2
+ import { addPrefix } from '../../functions/addPrefix.js';
3
+
4
+ export default ({ addUtilities, prefix = '' }) => {
5
+ const prefixed = addPrefix(wrapper, prefix);
6
+ addUtilities({ ...prefixed });
7
+ };
@@ -0,0 +1 @@
1
+ export default {".wrapper-full":{"width":"100%","max-width":"100%","box-sizing":"border-box","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-2xl":{"box-sizing":"border-box","max-width":"1536px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-lg":{"box-sizing":"border-box","max-width":"1024px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-md":{"box-sizing":"border-box","max-width":"768px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-sm":{"box-sizing":"border-box","max-width":"640px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-xl":{"box-sizing":"border-box","max-width":"1280px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper-xs":{"box-sizing":"border-box","max-width":"320px","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}},".wrapper":{"box-sizing":"border-box","margin-inline":"auto","padding-inline":"1rem","@media (width >= 40rem)":{"padding-inline":"2rem"},"@media (width >= 64rem)":{"padding-inline":"4rem"}}};
@@ -0,0 +1,2 @@
1
+ /*! frutjam v3.0.0 (c) 2026 Nezanuha | Released under the MIT License | https://frutjam.com */
2
+ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */.wrapper-full{box-sizing:border-box;margin-inline:auto;max-width:100%;padding-inline:1rem;width:100%;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-2xl{box-sizing:border-box;margin-inline:auto;max-width:1536px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-lg{box-sizing:border-box;margin-inline:auto;max-width:1024px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-md{box-sizing:border-box;margin-inline:auto;max-width:768px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-sm{box-sizing:border-box;margin-inline:auto;max-width:640px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-xl{box-sizing:border-box;margin-inline:auto;max-width:1280px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper-xs{box-sizing:border-box;margin-inline:auto;max-width:320px;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}.wrapper{box-sizing:border-box;margin-inline:auto;padding-inline:1rem;@media (width >= 40rem){padding-inline:2rem}@media (width >= 64rem){padding-inline:4rem}}
package/index.js ADDED
@@ -0,0 +1,272 @@
1
+ import { readFileSync, readdirSync, statSync, existsSync } from "fs"
2
+ import { join, dirname, basename, extname } from "path"
3
+ import { fileURLToPath } from "url"
4
+ import postcss from "postcss"
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dir = dirname(__filename)
8
+ const srcDir = join(__dir, "src")
9
+ const VERSION = JSON.parse(readFileSync(join(__dir, "package.json"), "utf8")).version
10
+
11
+ // ── Keys that must be injected after Tailwind processes layers ───────────────
12
+ // These files conflict with @layer utilities from Tailwind plugins (e.g. typography),
13
+ // so they are injected via OnceExit into @layer utilities (or root if not found).
14
+ const LATE_INJECT = new Set(['typography'])
15
+
16
+ // ── Registry (built once at startup, not per build) ──────────────────────────
17
+
18
+ function buildRegistry() {
19
+ const registry = {}
20
+
21
+ // Components: src/components/*/base.css — keyed by folder name
22
+ const componentsDir = join(srcDir, "components")
23
+ for (const name of readdirSync(componentsDir)) {
24
+ const entry = join(componentsDir, name, "base.css")
25
+ if (statSync(join(componentsDir, name)).isDirectory() && existsSync(entry)) {
26
+ registry[name] = entry
27
+ }
28
+ }
29
+
30
+ // Utilities: src/utilities/*.css — keyed by filename without extension
31
+ const utilitiesDir = join(srcDir, "utilities")
32
+ for (const file of readdirSync(utilitiesDir)) {
33
+ if (extname(file) === ".css") {
34
+ registry[basename(file, ".css")] = join(utilitiesDir, file)
35
+ }
36
+ }
37
+
38
+ return registry
39
+ }
40
+
41
+ const REGISTRY = buildRegistry()
42
+
43
+ // ── Helpers ─────────────────────────────────────────────────────────────────
44
+
45
+ function readFile(p) { return readFileSync(p, "utf8") }
46
+
47
+ function parseList(val) {
48
+ if (!val) return []
49
+ if (Array.isArray(val)) return val.map(s => s.trim()).filter(Boolean)
50
+ return String(val).split(",").map(s => s.trim()).filter(Boolean)
51
+ }
52
+
53
+ function applyPrefix(css, prefix) {
54
+ if (!prefix) return css
55
+
56
+ css = css.replace(
57
+ /@utility\s+([\w-]+\*?)\s*\{/g,
58
+ (_, name) => `@utility ${prefix}-${name} {`
59
+ )
60
+ css = css.replace(
61
+ /@apply\s+([^;{}\n]+)/g,
62
+ (_, classes) => `@apply ${classes.trim().split(/\s+/).map(c => `${prefix}-${c}`).join(' ')}`
63
+ )
64
+ css = css.replace(
65
+ /\/\*[\s\S]*?\*\/|"[^"]*"|'[^']*'|\.(([a-z][a-z0-9]*-?)+)/g,
66
+ (match, name) => name ? `.${prefix}-${name}` : match
67
+ )
68
+ return css
69
+ }
70
+
71
+ function buildUtilityMap(css) {
72
+ const map = new Map()
73
+ const pattern = /@utility\s+([\w-]+(?:\s*\*)?)\s*\{/g
74
+ let match
75
+ while ((match = pattern.exec(css)) !== null) {
76
+ const name = match[1].trim()
77
+ if (map.has(name)) continue
78
+ let depth = 0, bodyStart = -1
79
+ for (let i = match.index + match[0].length - 1; i < css.length; i++) {
80
+ if (css[i] === '{') {
81
+ if (depth === 0) bodyStart = i + 1
82
+ depth++
83
+ } else if (css[i] === '}') {
84
+ if (--depth === 0) { map.set(name, css.slice(bodyStart, i)); break }
85
+ }
86
+ }
87
+ }
88
+ return map
89
+ }
90
+
91
+ function resolveCopy(css, source = css) {
92
+ const map = source instanceof Map ? source : buildUtilityMap(source)
93
+ return css.replace(/@copy\s+([\w-]+)\s*;/g, (original, name) => map.get(name) ?? original)
94
+ }
95
+
96
+ function resolveImports(css, baseDir) {
97
+ return css.replace(
98
+ /@import\s+["']([^"']+)["'];?\s*/g,
99
+ (_, importPath) => {
100
+ const fullPath = join(baseDir, importPath)
101
+ const importedDir = dirname(fullPath)
102
+ const importedCSS = readFile(fullPath)
103
+ return resolveImports(importedCSS, importedDir)
104
+ }
105
+ )
106
+ }
107
+
108
+ function buildCustomThemes(themes) {
109
+ if (!themes || Object.keys(themes).length === 0) return ""
110
+
111
+ return Object.entries(themes)
112
+ .map(([name, vars]) => {
113
+ const varString = Object.entries(vars)
114
+ .map(([k, v]) => ` ${k}: ${v};`)
115
+ .join("\n")
116
+ return `[data-theme="${name}"] {\n${varString}\n}`
117
+ })
118
+ .join("\n\n")
119
+ }
120
+
121
+ // ── Base layers (inlined — no entry CSS files needed) ───────────────────────
122
+
123
+ const BASE_FULL = [
124
+ "base/reboot.css",
125
+ "theme/base.css",
126
+ ]
127
+
128
+ const BASE_CORE = [
129
+ "base/variants.css",
130
+ "base/tokens.css",
131
+ "theme/base.css",
132
+ ]
133
+
134
+ // ── Build ────────────────────────────────────────────────────────────────────
135
+
136
+ function buildCSS(prefix, themes, reset, rootSelector, include, exclude) {
137
+ // Resolve which components+utilities to include
138
+ let keys = Object.keys(REGISTRY)
139
+ if (include.length > 0) {
140
+ keys = include.filter(k => REGISTRY[k])
141
+ } else if (exclude.length > 0) {
142
+ const excludeSet = new Set(exclude)
143
+ keys = keys.filter(k => !excludeSet.has(k))
144
+ }
145
+ // LATE_INJECT keys are handled separately via OnceExit — skip them here
146
+ keys = keys.filter(k => !LATE_INJECT.has(k))
147
+
148
+ // Base layer (full with reboot, or core without)
149
+ const baseFiles = reset ? BASE_FULL : BASE_CORE
150
+ const baseCss = baseFiles
151
+ .map(f => resolveImports(readFile(join(srcDir, f)), join(srcDir, dirname(f))))
152
+ .join("\n")
153
+
154
+ // Components + utilities
155
+ const modulesCss = keys
156
+ .map(k => resolveImports(readFile(REGISTRY[k]), dirname(REGISTRY[k])))
157
+ .join("\n")
158
+
159
+ const combined = baseCss + "\n" + modulesCss
160
+ const utilMap = buildUtilityMap(combined)
161
+ let css = resolveCopy(combined, utilMap)
162
+
163
+ if (rootSelector !== ":root") {
164
+ css = css.replace(/:root\b/g, rootSelector)
165
+ }
166
+
167
+ css = applyPrefix(css, prefix)
168
+
169
+ const customThemes = buildCustomThemes(themes)
170
+ if (customThemes) css += "\n\n" + customThemes
171
+
172
+ return { css, utilMap }
173
+ }
174
+
175
+ function buildLateCSS(prefix, include, exclude, utilMap = new Map()) {
176
+ let keys = [...LATE_INJECT].filter(k => REGISTRY[k])
177
+ if (include.length > 0) {
178
+ keys = keys.filter(k => include.includes(k))
179
+ } else if (exclude.length > 0) {
180
+ const excludeSet = new Set(exclude)
181
+ keys = keys.filter(k => !excludeSet.has(k))
182
+ }
183
+ if (keys.length === 0) return ""
184
+ const raw = keys
185
+ .map(k => resolveImports(readFile(REGISTRY[k]), dirname(REGISTRY[k])))
186
+ .join("\n")
187
+ const css = resolveCopy(raw, utilMap)
188
+ return applyPrefix(css, prefix)
189
+ }
190
+
191
+ // ── Plugin ───────────────────────────────────────────────────────────────────
192
+
193
+ function parseAtPluginOptions(atRule) {
194
+ const opts = {}
195
+ atRule.each(node => {
196
+ if (node.type !== "decl") return
197
+ let val = node.value.trim()
198
+ if (val === "true") val = true
199
+ else if (val === "false") val = false
200
+ else if (
201
+ (val.startsWith('"') && val.endsWith('"')) ||
202
+ (val.startsWith("'") && val.endsWith("'"))
203
+ ) val = val.slice(1, -1)
204
+ opts[node.prop.trim()] = val
205
+ })
206
+ return opts
207
+ }
208
+
209
+ export { buildUtilityMap, resolveCopy }
210
+
211
+ export default function frutjam(options = {}) {
212
+ let _lateCss = ""
213
+
214
+ return {
215
+ postcssPlugin: "frutjam",
216
+ Once(root) {
217
+ // Collect @plugin "frutjam" { ... } blocks from the CSS
218
+ const atPluginNodes = []
219
+ root.each(node => {
220
+ if (
221
+ node.type === "atrule" &&
222
+ node.name === "plugin" &&
223
+ /^["']frutjam["']/.test(node.params.trim()) &&
224
+ node.nodes
225
+ ) atPluginNodes.push(node)
226
+ })
227
+
228
+ // CSS-level options override factory options
229
+ let cssOptions = {}
230
+ for (const node of atPluginNodes) {
231
+ Object.assign(cssOptions, parseAtPluginOptions(node))
232
+ node.remove()
233
+ }
234
+ const merged = { ...options, ...cssOptions }
235
+
236
+ const prefix = typeof merged.prefix === "string" ? merged.prefix.trim() : ""
237
+ const themes = typeof merged.themes === "object" ? merged.themes : {}
238
+ const reset = merged.reset !== false
239
+ const rootSelector = typeof merged.root === "string" ? merged.root : ":root"
240
+ const logs = merged.logs !== false
241
+ const include = parseList(merged.include)
242
+ const exclude = parseList(merged.exclude)
243
+
244
+ const { css, utilMap } = buildCSS(prefix, themes, reset, rootSelector, include, exclude)
245
+ root.append(postcss.parse(css).nodes)
246
+ _lateCss = buildLateCSS(prefix, include, exclude, utilMap)
247
+ if (logs) {
248
+ const d = "\x1b[2m", r = "\x1b[0m"
249
+ const badge = "\x1b[46m\x1b[30m frutjam \x1b[0m"
250
+ console.log("\n" + badge + " " + d + "v" + VERSION + r + "\n")
251
+ }
252
+ },
253
+ OnceExit(root) {
254
+ if (!_lateCss) return
255
+ // Inject into @layer utilities if it exists (correct cascade layer),
256
+ // otherwise append to root (unlayered CSS beats all @layer rules).
257
+ let utilLayer = null
258
+ root.each(node => {
259
+ if (node.type === "atrule" && node.name === "layer" && node.params === "utilities" && node.nodes) {
260
+ utilLayer = node
261
+ }
262
+ })
263
+ const nodes = postcss.parse(_lateCss).nodes
264
+ if (utilLayer) {
265
+ utilLayer.append(nodes)
266
+ } else {
267
+ root.append(nodes)
268
+ }
269
+ }
270
+ }
271
+ }
272
+ frutjam.postcss = true
package/package.json CHANGED
@@ -1,42 +1,51 @@
1
1
  {
2
2
  "name": "frutjam",
3
- "version": "2.7.1-beta.1",
4
- "description": "A utility-first CSS framework UI Library",
5
- "funding": "https://ko-fi.com/nezanuha",
6
- "main": "./dist/frutjam.local.css",
7
- "browser": "dist/frutjam.local.css",
8
- "style": "./dist/frutjam.local.css",
3
+ "version": "3.0.0",
4
+ "description": "A utility-first CSS UI Library for Tailwind CSS",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./dist/plugin.js",
8
+ "./postcss": "./index.js",
9
+ "./js": "./packages/js/index.js",
10
+ "./js/*": "./packages/js/*.js",
11
+ "./react": "./packages/react/index.js",
12
+ "./react/*": "./packages/react/*.js",
13
+ "./components/*": "./dist/components/*/index.js",
14
+ "./utilities/*": "./dist/utilities/*/index.js",
15
+ "./themes/*": "./dist/themes/*/index.js",
16
+ "./base/tokens": "./dist/base/tokens/index.js",
17
+ "./base/reboot": "./dist/base/reboot/index.js",
18
+ "./functions/addPrefix": "./dist/functions/addPrefix.js"
19
+ },
9
20
  "files": [
21
+ "index.js",
10
22
  "dist/",
11
- "README.md",
12
- "LICENSE"
23
+ "packages/"
13
24
  ],
25
+ "style": "./dist/frutjam.min.css",
26
+ "scripts": {
27
+ "clean": "rimraf ./dist",
28
+ "build:cdn": "node scripts/build-cdn.mjs",
29
+ "build:plugin": "node scripts/build-plugin.mjs",
30
+ "build": "rimraf ./dist && node scripts/build-cdn.mjs && node scripts/build-plugin.mjs",
31
+ "prepublishOnly": "rimraf ./dist && node scripts/build-cdn.mjs && node scripts/build-plugin.mjs",
32
+ "postversion": "git push origin main --tags"
33
+ },
34
+ "funding": "https://ko-fi.com/nezanuha",
14
35
  "repository": {
15
36
  "type": "git",
16
37
  "url": "git+https://github.com/nezanuha/frutjam.git"
17
38
  },
18
- "exports": {
19
- ".": "./dist/frutjam.local.css",
20
- "./safelist": "./dist/frutjam.safelist.local.css"
21
- },
22
- "scripts": {
23
- "clean": "rimraf ./dist",
24
- "dev": "webpack serve --config scripts/webpack.config.dev.js --mode=development",
25
- "build:local": "node scripts/build-local.mjs && node scripts/build-safelist-local.mjs",
26
- "build:browser": "webpack --config scripts/webpack.config.prod.js --mode=production",
27
- "build": "npm run clean && npm run build:browser && npm run build:local",
28
- "prepublishOnly": "npm run build"
29
- },
30
39
  "publishConfig": {
31
- "provenance": true,
32
- "access": "public"
40
+ "access": "public",
41
+ "provenance": true
33
42
  },
34
43
  "keywords": [
35
44
  "frutjam",
36
45
  "tailwindcss",
46
+ "tailwind css plugin",
37
47
  "UI Library",
38
48
  "tailwind",
39
- "taiwind css",
40
49
  "components",
41
50
  "tailwind components",
42
51
  "tailwind component library",
@@ -49,11 +58,11 @@
49
58
  "tailwind django",
50
59
  "tailwind rails",
51
60
  "tailwind laravel",
52
- "tailwind css plugin",
53
61
  "component library",
54
62
  "tailwind themes",
55
63
  "css library",
56
64
  "theme",
65
+ "design system",
57
66
  "front-end",
58
67
  "framework",
59
68
  "W3C standards",
@@ -68,23 +77,25 @@
68
77
  "bugs": {
69
78
  "url": "https://github.com/nezanuha/frutjam/issues"
70
79
  },
71
- "dependencies": {
72
- "@tailwindcss/postcss": "^4.1.14",
73
- "tailwindcss": "^4.1.14"
80
+ "engines": {
81
+ "node": ">=18"
82
+ },
83
+ "peerDependencies": {
84
+ "tailwindcss": "^4.0.0",
85
+ "react": ">=18"
86
+ },
87
+ "peerDependenciesMeta": {
88
+ "react": {
89
+ "optional": true
90
+ }
74
91
  },
75
92
  "devDependencies": {
76
- "chalk": "^5.6.2",
77
- "css-loader": "^7.1.2",
78
- "mini-css-extract-plugin": "^2.9.4",
79
- "postcss": "^8.5.6",
80
- "postcss-import": "^16.1.1",
81
- "postcss-loader": "^8.2.0",
82
- "prettier-plugin-tailwindcss": "^0.7.0",
83
- "rimraf": "^6.0.1",
84
- "style-loader": "^4.0.0",
85
- "webpack": "^5.102.1",
86
- "webpack-cli": "^6.0.1",
87
- "webpack-dev-server": "^5.2.2",
88
- "webpack-remove-empty-scripts": "^1.1.1"
93
+ "@tailwindcss/postcss": "^4.2.4",
94
+ "autoprefixer": "^10.5.0",
95
+ "cssnano": "^7.1.7",
96
+ "postcss": "^8.5.13",
97
+ "postcss-js": "^5.1.0",
98
+ "rimraf": "^6.1.3",
99
+ "tailwindcss": "^4.2.4"
89
100
  }
90
101
  }
@@ -0,0 +1,19 @@
1
+ export function createCarousel(el) {
2
+ const getItems = () => Array.from(el.querySelectorAll('.carousel-item'))
3
+ let current = 0
4
+
5
+ function goTo(index) {
6
+ const items = getItems()
7
+ if (!items.length) return
8
+ current = Math.max(0, Math.min(index, items.length - 1))
9
+ items[current].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' })
10
+ }
11
+
12
+ return {
13
+ goTo,
14
+ next: () => goTo(current + 1),
15
+ prev: () => goTo(current - 1),
16
+ current: () => current,
17
+ count: () => getItems().length,
18
+ }
19
+ }
@@ -0,0 +1,8 @@
1
+ export function createCollapsible(el) {
2
+ return {
3
+ open: () => el.classList.add('collapsible-open'),
4
+ close: () => el.classList.remove('collapsible-open'),
5
+ toggle: () => el.classList.toggle('collapsible-open'),
6
+ isOpen: () => el.classList.contains('collapsible-open'),
7
+ }
8
+ }
@@ -0,0 +1,79 @@
1
+ export function createCombobox(el) {
2
+ const input = el.querySelector('input')
3
+ let activeIndex = -1
4
+
5
+ const allOptions = () => Array.from(el.querySelectorAll('[role="option"], .combobox-option'))
6
+ const visibleOptions = () => allOptions().filter((o) => !o.hidden)
7
+
8
+ function open() {
9
+ el.classList.add('combobox-open')
10
+ input?.setAttribute('aria-expanded', 'true')
11
+ }
12
+
13
+ function close() {
14
+ el.classList.remove('combobox-open')
15
+ input?.setAttribute('aria-expanded', 'false')
16
+ highlight(-1)
17
+ }
18
+
19
+ function toggle() {
20
+ el.classList.contains('combobox-open') ? close() : open()
21
+ }
22
+
23
+ function filter(query) {
24
+ allOptions().forEach((opt) => {
25
+ opt.hidden = !opt.textContent.toLowerCase().includes(query.toLowerCase())
26
+ })
27
+ highlight(-1)
28
+ }
29
+
30
+ function highlight(index) {
31
+ const opts = visibleOptions()
32
+ opts.forEach((o) => o.classList.remove('combobox-option-active'))
33
+ if (index >= 0 && opts[index]) {
34
+ opts[index].classList.add('combobox-option-active')
35
+ opts[index].scrollIntoView({ block: 'nearest' })
36
+ }
37
+ activeIndex = index
38
+ }
39
+
40
+ function select(opt) {
41
+ if (input) input.value = opt.textContent.trim()
42
+ el.dispatchEvent(new CustomEvent('fj:select', { detail: { value: opt.dataset.value ?? opt.textContent.trim() } }))
43
+ close()
44
+ }
45
+
46
+ input?.addEventListener('input', (e) => {
47
+ open()
48
+ filter(e.target.value)
49
+ })
50
+
51
+ input?.addEventListener('keydown', (e) => {
52
+ const opts = visibleOptions()
53
+ if (e.key === 'ArrowDown') {
54
+ e.preventDefault()
55
+ if (!el.classList.contains('combobox-open')) { open(); return }
56
+ highlight(Math.min(activeIndex + 1, opts.length - 1))
57
+ } else if (e.key === 'ArrowUp') {
58
+ e.preventDefault()
59
+ highlight(Math.max(activeIndex - 1, 0))
60
+ } else if (e.key === 'Enter' && activeIndex >= 0) {
61
+ e.preventDefault()
62
+ select(opts[activeIndex])
63
+ } else if (e.key === 'Escape') {
64
+ close()
65
+ }
66
+ })
67
+
68
+ el.addEventListener('click', (e) => {
69
+ const opt = e.target.closest('[role="option"], .combobox-option')
70
+ if (opt) { select(opt); return }
71
+ if (e.target === input) open()
72
+ })
73
+
74
+ document.addEventListener('click', (e) => {
75
+ if (!el.contains(e.target)) close()
76
+ }, { capture: true })
77
+
78
+ return { open, close, toggle, filter }
79
+ }
@@ -0,0 +1 @@
1
+ export { createModal as createDrawer } from './modal.js'
@@ -0,0 +1,8 @@
1
+ export { createModal } from './modal.js'
2
+ export { createDrawer } from './drawer.js'
3
+ export { createCollapsible } from './collapsible.js'
4
+ export { createTooltip } from './tooltip.js'
5
+ export { createTabs } from './tabs.js'
6
+ export { createToastManager } from './toast.js'
7
+ export { createCarousel } from './carousel.js'
8
+ export { createCombobox } from './combobox.js'