@fakhrirafiki/theme-engine 0.4.5 → 0.4.8

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.
package/dist/index.mjs CHANGED
@@ -3940,6 +3940,29 @@ function ThemeScript({ presetStorageKey = "theme-preset", defaultPreset }) {
3940
3940
  if (rgb) return toTriplet(rgbToHsl(rgb.r, rgb.g, rgb.b));
3941
3941
  }
3942
3942
 
3943
+ // Any other CSS color (e.g. oklch(...), named colors) -> resolve via computed styles.
3944
+ try {
3945
+ const probe = document.createElement('span');
3946
+ probe.style.color = trimmed;
3947
+ probe.style.position = 'absolute';
3948
+ probe.style.left = '-9999px';
3949
+ probe.style.top = '-9999px';
3950
+ probe.style.visibility = 'hidden';
3951
+ document.documentElement.appendChild(probe);
3952
+ const computed = getComputedStyle(probe).color; // rgb(...) or rgba(...)
3953
+ probe.remove();
3954
+
3955
+ const match = computed && computed.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/i);
3956
+ if (match) {
3957
+ const r = clamp(parseFloat(match[1]) || 0, 0, 255);
3958
+ const g = clamp(parseFloat(match[2]) || 0, 0, 255);
3959
+ const b = clamp(parseFloat(match[3]) || 0, 0, 255);
3960
+ return toTriplet(rgbToHsl(r, g, b));
3961
+ }
3962
+ } catch (e) {
3963
+ // ignore and fall through
3964
+ }
3965
+
3943
3966
  return value;
3944
3967
  }
3945
3968
 
@@ -4212,23 +4235,40 @@ function normalizeColorValueToHslTriplet(value) {
4212
4235
  if (parsedHsl) return formatHSL(parsedHsl, false);
4213
4236
  const parsedRgb = parseHex(trimmed);
4214
4237
  if (parsedRgb) return formatHSL(rgbToHsl(parsedRgb), false);
4215
- return value;
4238
+ if (typeof document !== "undefined") {
4239
+ try {
4240
+ if (trimmed.startsWith("var(")) return trimmed;
4241
+ const probe = document.createElement("span");
4242
+ probe.style.color = trimmed;
4243
+ probe.style.position = "absolute";
4244
+ probe.style.left = "-9999px";
4245
+ probe.style.top = "-9999px";
4246
+ probe.style.visibility = "hidden";
4247
+ document.documentElement.appendChild(probe);
4248
+ const computed = getComputedStyle(probe).color;
4249
+ probe.remove();
4250
+ const match = computed.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
4251
+ if (match) {
4252
+ const r = Number(match[1]);
4253
+ const g = Number(match[2]);
4254
+ const b = Number(match[3]);
4255
+ if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
4256
+ return formatHSL(rgbToHsl({ r, g, b }), false);
4257
+ }
4258
+ }
4259
+ } catch {
4260
+ }
4261
+ }
4262
+ return trimmed;
4216
4263
  }
4217
4264
  function ThemeProvider({
4218
4265
  children,
4219
4266
  defaultMode = "system",
4220
4267
  defaultPreset,
4221
- enableTransitions = true,
4222
4268
  modeStorageKey = THEME_STORAGE_KEY,
4223
4269
  presetStorageKey = "theme-preset",
4224
- enablePresets = true,
4225
- customPresets,
4226
- includeBuiltInPresets = true,
4227
- disableScript = false,
4228
- deferRenderUntilReady
4270
+ customPresets
4229
4271
  }) {
4230
- const shouldInjectScript = !disableScript;
4231
- const shouldDeferRenderUntilReady = deferRenderUntilReady ?? disableScript;
4232
4272
  const normalizedCustomPresets = useMemo2(() => customPresets ?? {}, [customPresets]);
4233
4273
  const [mode, setMode] = useState(() => {
4234
4274
  const stored = getStoredMode(modeStorageKey);
@@ -4242,9 +4282,7 @@ function ThemeProvider({
4242
4282
  });
4243
4283
  const availablePresets = useMemo2(() => {
4244
4284
  const merged = {};
4245
- if (includeBuiltInPresets) {
4246
- Object.assign(merged, tweakcnPresets);
4247
- }
4285
+ Object.assign(merged, tweakcnPresets);
4248
4286
  if (Object.keys(normalizedCustomPresets).length > 0) {
4249
4287
  const validationResult = validateCustomPresets(normalizedCustomPresets);
4250
4288
  const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
@@ -4260,8 +4298,8 @@ function ThemeProvider({
4260
4298
  }
4261
4299
  }
4262
4300
  return merged;
4263
- }, [includeBuiltInPresets, normalizedCustomPresets]);
4264
- const builtInPresets = useMemo2(() => includeBuiltInPresets ? tweakcnPresets : {}, [includeBuiltInPresets]);
4301
+ }, [normalizedCustomPresets]);
4302
+ const builtInPresets = tweakcnPresets;
4265
4303
  const getAvailablePresetById = useCallback(
4266
4304
  (id) => {
4267
4305
  return availablePresets[id] || null;
@@ -4269,12 +4307,8 @@ function ThemeProvider({
4269
4307
  [availablePresets]
4270
4308
  );
4271
4309
  const [currentPreset, setCurrentPreset] = useState(null);
4272
- const [isReady, setIsReady] = useState(() => !shouldDeferRenderUntilReady);
4273
4310
  useEffect(() => {
4274
- if (!enablePresets || typeof window === "undefined") {
4275
- setIsReady(true);
4276
- return;
4277
- }
4311
+ if (typeof window === "undefined") return;
4278
4312
  try {
4279
4313
  const stored = localStorage.getItem(presetStorageKey);
4280
4314
  const isDevelopment = typeof window !== "undefined" && window.location?.hostname === "localhost";
@@ -4327,10 +4361,8 @@ function ThemeProvider({
4327
4361
  if (isDevelopment) {
4328
4362
  console.warn("\u{1F3A8} UnifiedTheme: Failed to load preset from storage:", error);
4329
4363
  }
4330
- } finally {
4331
- setIsReady(true);
4332
4364
  }
4333
- }, [presetStorageKey, enablePresets, defaultPreset, getAvailablePresetById]);
4365
+ }, [presetStorageKey, defaultPreset, getAvailablePresetById]);
4334
4366
  useEffect(() => {
4335
4367
  if (mode === "system") {
4336
4368
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
@@ -4409,7 +4441,7 @@ function ThemeProvider({
4409
4441
  (coordinates) => {
4410
4442
  const newMode = resolvedMode === "light" ? "dark" : "light";
4411
4443
  const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
4412
- if (enableTransitions && !prefersReducedMotion && typeof document !== "undefined" && "startViewTransition" in document) {
4444
+ if (!prefersReducedMotion && typeof document !== "undefined" && "startViewTransition" in document) {
4413
4445
  const root = document.documentElement;
4414
4446
  if (coordinates) {
4415
4447
  root.style.setProperty("--x", `${coordinates.x}px`);
@@ -4422,7 +4454,7 @@ function ThemeProvider({
4422
4454
  handleModeChange(newMode);
4423
4455
  }
4424
4456
  },
4425
- [resolvedMode, enableTransitions, handleModeChange]
4457
+ [resolvedMode, handleModeChange]
4426
4458
  );
4427
4459
  useEffect(() => {
4428
4460
  if (!currentPreset || typeof window === "undefined") return;
@@ -4437,7 +4469,7 @@ function ThemeProvider({
4437
4469
  appliedAt: Date.now()
4438
4470
  };
4439
4471
  setCurrentPreset(presetData);
4440
- if (enablePresets && typeof window !== "undefined") {
4472
+ if (typeof window !== "undefined") {
4441
4473
  try {
4442
4474
  localStorage.setItem(presetStorageKey, JSON.stringify(presetData));
4443
4475
  } catch (error) {
@@ -4448,7 +4480,7 @@ function ThemeProvider({
4448
4480
  applyPresetColors(preset.colors, resolvedMode);
4449
4481
  }
4450
4482
  },
4451
- [presetStorageKey, enablePresets, applyPresetColors, resolvedMode]
4483
+ [presetStorageKey, applyPresetColors, resolvedMode]
4452
4484
  );
4453
4485
  const setThemePresetById = useCallback(
4454
4486
  (presetId) => {
@@ -4472,53 +4504,50 @@ function ThemeProvider({
4472
4504
  [getAvailablePresetById, applyPreset]
4473
4505
  );
4474
4506
  const clearPreset = useCallback(() => {
4475
- if (enablePresets && typeof window !== "undefined") {
4507
+ if (typeof window !== "undefined") {
4476
4508
  try {
4477
4509
  localStorage.removeItem(presetStorageKey);
4478
4510
  } catch (error) {
4479
4511
  console.error("\u{1F3A8} UnifiedTheme: Failed to clear preset:", error);
4480
4512
  }
4481
- if (defaultPreset) {
4482
- const preset = getAvailablePresetById(defaultPreset);
4483
- if (preset) {
4484
- const presetData = {
4485
- presetId: defaultPreset,
4486
- presetName: preset.label,
4487
- colors: {
4488
- light: preset.styles.light,
4489
- dark: preset.styles.dark
4490
- },
4491
- appliedAt: Date.now()
4492
- };
4493
- setCurrentPreset(presetData);
4494
- applyPresetColors(presetData.colors, resolvedMode);
4495
- } else {
4496
- console.warn("\u{1F3A8} UnifiedTheme: Default preset not found:", defaultPreset);
4497
- setCurrentPreset(null);
4498
- }
4513
+ }
4514
+ if (defaultPreset) {
4515
+ const preset = getAvailablePresetById(defaultPreset);
4516
+ if (preset) {
4517
+ const presetData = {
4518
+ presetId: defaultPreset,
4519
+ presetName: preset.label,
4520
+ colors: {
4521
+ light: preset.styles.light,
4522
+ dark: preset.styles.dark
4523
+ },
4524
+ appliedAt: Date.now()
4525
+ };
4526
+ setCurrentPreset(presetData);
4527
+ applyPresetColors(presetData.colors, resolvedMode);
4499
4528
  } else {
4529
+ console.warn("\u{1F3A8} UnifiedTheme: Default preset not found:", defaultPreset);
4500
4530
  setCurrentPreset(null);
4501
- const root = document.documentElement;
4502
- const allProperties = [
4503
- ...CSS_PROPERTY_CATEGORIES.colors,
4504
- ...CSS_PROPERTY_CATEGORIES.typography,
4505
- ...CSS_PROPERTY_CATEGORIES.layout,
4506
- ...CSS_PROPERTY_CATEGORIES.shadows,
4507
- ...CSS_PROPERTY_CATEGORIES.spacing
4508
- ];
4509
- let clearedCount = 0;
4510
- allProperties.forEach((prop) => {
4511
- const cssVar = `--${prop}`;
4512
- root.style.removeProperty(cssVar);
4513
- clearedCount++;
4514
- });
4515
4531
  }
4532
+ } else {
4533
+ setCurrentPreset(null);
4534
+ const root = document.documentElement;
4535
+ const allProperties = [
4536
+ ...CSS_PROPERTY_CATEGORIES.colors,
4537
+ ...CSS_PROPERTY_CATEGORIES.typography,
4538
+ ...CSS_PROPERTY_CATEGORIES.layout,
4539
+ ...CSS_PROPERTY_CATEGORIES.shadows,
4540
+ ...CSS_PROPERTY_CATEGORIES.spacing
4541
+ ];
4542
+ let clearedCount = 0;
4543
+ allProperties.forEach((prop) => {
4544
+ const cssVar = `--${prop}`;
4545
+ root.style.removeProperty(cssVar);
4546
+ clearedCount++;
4547
+ });
4516
4548
  }
4517
- }, [presetStorageKey, enablePresets, defaultPreset, applyPresetColors, resolvedMode]);
4518
- const scriptElement = shouldInjectScript ? /* @__PURE__ */ jsx2(ThemeScript, { presetStorageKey, defaultPreset }) : null;
4519
- if (!isReady && shouldDeferRenderUntilReady) {
4520
- return scriptElement;
4521
- }
4549
+ }, [presetStorageKey, defaultPreset, getAvailablePresetById, applyPresetColors, resolvedMode]);
4550
+ const scriptElement = /* @__PURE__ */ jsx2(ThemeScript, { presetStorageKey, defaultPreset });
4522
4551
  const isUsingDefaultPreset = !!defaultPreset && currentPreset?.presetId === defaultPreset;
4523
4552
  const contextValue = {
4524
4553
  mode,
@@ -4774,7 +4803,7 @@ var AnimatedRow = ({
4774
4803
  }) => {
4775
4804
  if (presets.length === 0) return null;
4776
4805
  const duplicatedPresets = Array(animation.duplicationFactor).fill(presets).flat();
4777
- const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + presets.length * layout.buttonGap;
4806
+ const totalWidth = presets.reduce((sum, preset) => sum + (Number(preset.metadata?.buttonWidth) || layout.buttonWidth), 0) + Math.max(0, presets.length - 1) * layout.buttonGap;
4778
4807
  const effectiveScrollSpeed = Math.max(0.1, animation.scrollSpeed || 1);
4779
4808
  const animationDuration = presets.length * animation.duration / effectiveScrollSpeed;
4780
4809
  return /* @__PURE__ */ jsx4(
@@ -4971,6 +5000,42 @@ function useTypedTheme(customPresets) {
4971
5000
  setThemePresetById
4972
5001
  };
4973
5002
  }
5003
+
5004
+ // src/hooks/useThemeEngine.ts
5005
+ function useThemeEngine() {
5006
+ const theme = useTheme();
5007
+ const darkMode = theme.resolvedMode === "dark";
5008
+ const setDarkMode = (mode) => theme.setMode(mode);
5009
+ const toggleDarkMode = (coordinates) => theme.toggleMode(coordinates);
5010
+ const applyThemeById = (themeId) => theme.setThemePresetById(themeId);
5011
+ const applyPresetById = applyThemeById;
5012
+ const clearTheme = () => theme.clearPreset();
5013
+ const clearPreset = clearTheme;
5014
+ const currentTheme = theme.currentPreset;
5015
+ const currentPreset = theme.currentPreset;
5016
+ return {
5017
+ // Mode
5018
+ darkMode,
5019
+ mode: theme.mode,
5020
+ resolvedMode: theme.resolvedMode,
5021
+ setDarkMode,
5022
+ setMode: theme.setMode,
5023
+ toggleDarkMode,
5024
+ toggleMode: theme.toggleMode,
5025
+ // Presets (theme naming)
5026
+ applyThemeById,
5027
+ applyPresetById,
5028
+ clearTheme,
5029
+ clearPreset,
5030
+ currentTheme,
5031
+ currentPreset,
5032
+ // Advanced / diagnostics
5033
+ isUsingDefaultPreset: theme.isUsingDefaultPreset,
5034
+ availablePresets: theme.availablePresets,
5035
+ builtInPresets: theme.builtInPresets,
5036
+ customPresets: theme.customPresets
5037
+ };
5038
+ }
4974
5039
  export {
4975
5040
  ThemePresetButtons,
4976
5041
  ThemeProvider,
@@ -4987,6 +5052,7 @@ export {
4987
5052
  searchPresets,
4988
5053
  tweakcnPresets,
4989
5054
  useTheme,
5055
+ useThemeEngine,
4990
5056
  useTypedTheme,
4991
5057
  validateCustomPresets,
4992
5058
  validateTweakCNPreset,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fakhrirafiki/theme-engine",
3
- "version": "0.4.5",
3
+ "version": "0.4.8",
4
4
  "description": "Elegant theming system with smooth transitions, custom presets, semantic accent colors, and complete shadcn/ui support for modern React applications",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",