-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
405 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package twmerge | ||
|
||
import ( | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
type GetClassGroupIdfn func(string) (isTwClass bool, groupId string) | ||
|
||
func MakeGetClassGroupId(conf *TwMergeConfig) GetClassGroupIdfn { | ||
var getClassGroupIdRecursive func(classParts []string, i int, classMap *ClassPart) (isTwClass bool, groupId string) | ||
getClassGroupIdRecursive = func(classParts []string, i int, classMap *ClassPart) (isTwClass bool, groupId string) { | ||
if i >= len(classParts) { | ||
if classMap.ClassGroupId != "" { | ||
return true, classMap.ClassGroupId | ||
} | ||
|
||
return false, "" | ||
} | ||
|
||
if classMap.NextPart != nil { | ||
nextClassMap := classMap.NextPart[classParts[i]] | ||
isTw, id := getClassGroupIdRecursive(classParts, i+1, &nextClassMap) | ||
if isTw { | ||
return isTw, id | ||
} | ||
} | ||
|
||
if classMap.Validators != nil && len(classMap.Validators) > 0 { | ||
remainingClass := strings.Join(classParts[i:], string(conf.ClassSeparator)) | ||
|
||
for _, validator := range classMap.Validators { | ||
if validator.Fn(remainingClass) { | ||
return true, validator.ClassGroupId | ||
} | ||
} | ||
|
||
} | ||
return false, "" | ||
} | ||
|
||
var arbitraryPropertyRegex = regexp.MustCompile(`^\[(.+)\]$`) | ||
|
||
getGroupIdForArbitraryProperty := func(class string) (bool, string) { | ||
if arbitraryPropertyRegex.MatchString(class) { | ||
arbitraryPropertyClassName := arbitraryPropertyRegex.FindStringSubmatch(class)[1] | ||
property := arbitraryPropertyClassName[:strings.Index(arbitraryPropertyClassName, ":")] | ||
|
||
if property != "" { | ||
// I use two dots here because one dot is used as prefix for class groups in plugins | ||
return true, "arbitrary.." + property | ||
} | ||
} | ||
|
||
return false, "" | ||
} | ||
|
||
return func(baseClass string) (isTwClass bool, groupdId string) { | ||
|
||
classParts := strings.Split(baseClass, string(conf.ClassSeparator)) | ||
// remove first element if empty for things like -px-4 | ||
if len(classParts) > 0 && classParts[0] == "" { | ||
classParts = classParts[1:] | ||
} | ||
isTwClass, groupId := getClassGroupIdRecursive(classParts, 0, &conf.ClassGroups) | ||
if isTwClass { | ||
return isTwClass, groupId | ||
} | ||
|
||
return getGroupIdForArbitraryProperty(baseClass) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package twmerge | ||
|
||
import ( | ||
"strings" | ||
|
||
lru "github.com/Oudwins/tailwind-merge-go/pkg/cache" | ||
) | ||
|
||
// create the config (just gets the config passed in) | ||
|
||
// create the config utils | ||
// LRU cache | ||
// split modifiers | ||
// -> for things like hover:bg-x | ||
// class utils | ||
// -> for splitting classes | ||
|
||
// cache get & set | ||
|
||
// merge fn | ||
// 1. check cache | ||
// 2. mergeClassList | ||
// 3. set cache | ||
|
||
// should this also take a cache directly? | ||
func CreateTwMerge(config *TwMergeConfig, cache lru.Cache) func(args ...string) string { | ||
if config == nil { | ||
config = MakeDefaultConfig() | ||
} | ||
if cache == nil { | ||
cache = lru.Make(config.MaxCacheSize) | ||
} | ||
|
||
splitModifiers := MakeSplitModifiers(config) | ||
|
||
getClassGroupId := MakeGetClassGroupId(config) | ||
|
||
mergeClassList := MakeMergeClassList(config, splitModifiers, getClassGroupId) | ||
|
||
return func(args ...string) string { | ||
classList := strings.Join(args, " ") | ||
cached := cache.Get(classList) | ||
if cached != "" { | ||
return cached | ||
} | ||
// check if in cache | ||
merged := mergeClassList(classList) | ||
cache.Set(classList, merged) | ||
return merged | ||
} | ||
} | ||
|
||
var Merge = CreateTwMerge(nil, nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package twmerge | ||
|
||
import ( | ||
"regexp" | ||
"slices" | ||
"strings" | ||
) | ||
|
||
const SPLIT_CLASSES_REGEX = `\s+` | ||
|
||
var splitPattern = regexp.MustCompile(SPLIT_CLASSES_REGEX) | ||
|
||
func MakeMergeClassList(conf *TwMergeConfig, splitModifiers SplitModifiersFn, getClassGroupId GetClassGroupIdfn) func(classList string) string { | ||
return func(classList string) string { | ||
classes := splitPattern.Split(strings.TrimSpace(classList), -1) | ||
unqClasses := make(map[string]string, len(classes)) | ||
resultClassList := "" | ||
|
||
for _, class := range classes { | ||
baseClass, modifiers, hasImportant, maybePostfixModPosition := splitModifiers(class) | ||
|
||
// there is a postfix modifier -> text-lg/8 | ||
if maybePostfixModPosition != -1 { | ||
baseClass = baseClass[:maybePostfixModPosition] | ||
} | ||
isTwClass, groupId := getClassGroupId(baseClass) | ||
if !isTwClass { | ||
resultClassList += class + " " | ||
continue | ||
} | ||
// we have to sort the modifiers bc hover:focus:bg-red-500 == focus:hover:bg-red-500 | ||
modifiers = SortModifiers(modifiers) | ||
if hasImportant { | ||
modifiers = append(modifiers, "!") | ||
} | ||
unqClasses[groupId+strings.Join(modifiers, string(conf.ModifierSeparator))] = class | ||
|
||
conflicts := conf.ConflictingClassGroups[groupId] | ||
if conflicts == nil { | ||
continue | ||
} | ||
for _, conflict := range conflicts { | ||
// erase the conflicts with the same modifiers | ||
unqClasses[conflict+strings.Join(modifiers, string(conf.ModifierSeparator))] = "" | ||
} | ||
} | ||
|
||
for _, class := range unqClasses { | ||
if class == "" { | ||
continue | ||
} | ||
resultClassList += class + " " | ||
} | ||
return strings.TrimSpace(resultClassList) | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Sorts modifiers according to following schema: | ||
* - Predefined modifiers are sorted alphabetically | ||
* - When an arbitrary variant appears, it must be preserved which modifiers are before and after it | ||
*/ | ||
func SortModifiers(modifiers []string) []string { | ||
if modifiers == nil || len(modifiers) < 2 { | ||
return modifiers | ||
} | ||
|
||
unsortedModifiers := []string{} | ||
sorted := make([]string, len(modifiers)) | ||
|
||
for _, modifier := range modifiers { | ||
isArbitraryVariant := modifier[0] == '[' | ||
if isArbitraryVariant { | ||
slices.Sort(unsortedModifiers) | ||
sorted = append(sorted, unsortedModifiers...) | ||
sorted = append(sorted, modifier) | ||
unsortedModifiers = []string{} | ||
continue | ||
} | ||
unsortedModifiers = append(unsortedModifiers, modifier) | ||
} | ||
|
||
slices.Sort(unsortedModifiers) | ||
sorted = append(sorted, unsortedModifiers...) | ||
|
||
return sorted | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package twmerge | ||
|
||
type SplitModifiersFn = func(string) (baseClass string, modifiers []string, hasImportant bool, maybePostfixModPosition int) | ||
|
||
func MakeSplitModifiers(conf *TwMergeConfig) SplitModifiersFn { | ||
separator := conf.ModifierSeparator | ||
|
||
return func(className string) (string, []string, bool, int) { | ||
modifiers := []string{} | ||
modifierStart := 0 | ||
bracketDepth := 0 | ||
// used for bg-red-500/50 (50% opacity) | ||
maybePostfixModPosition := -1 | ||
|
||
for i := 0; i < len(className); i++ { | ||
char := rune(className[i]) | ||
|
||
if char == '[' { | ||
bracketDepth++ | ||
continue | ||
} | ||
if char == ']' { | ||
bracketDepth-- | ||
continue | ||
} | ||
|
||
if bracketDepth == 0 { | ||
if char == separator { | ||
modifiers = append(modifiers, className[modifierStart:i]) | ||
modifierStart = i + 1 | ||
continue | ||
} | ||
|
||
if char == conf.PostfixModifier { | ||
maybePostfixModPosition = i | ||
} | ||
} | ||
} | ||
|
||
baseClassWithImportant := className[modifierStart:] | ||
hasImportant := baseClassWithImportant[0] == byte(conf.ImportantModifier) | ||
var baseClass string | ||
if hasImportant { | ||
baseClass = baseClassWithImportant[1:] | ||
} else { | ||
baseClass = baseClassWithImportant | ||
} | ||
|
||
return baseClass, modifiers, hasImportant, maybePostfixModPosition | ||
|
||
} | ||
} |
Oops, something went wrong.