All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.directive.class.class.js Maven / Gradle / Ivy

import { hasAnimate, isObject, isString } from "../../shared/utils";

function classDirective(name, selector) {
  name = `ngClass${name}`;
  let indexWatchExpression;

  return [
    "$parse",
    /**
     *
     * @param {import("../../core/parse/parse.js").ParseService} $parse
     * @returns {import("../../types").Directive}
     */
    ($parse) => ({
      restrict: "EA",
      /**
       * @param {import("../../core/scope/scope").Scope} scope
       * @param {import("../../shared/jqlite/jqlite").JQLite} element
       * @param {import("../../core/compile/attributes").Attributes} attr
       */
      link(scope, element, attr) {
        let classCounts = element.data("$classCounts");
        let oldModulo = true;
        let oldClassString;

        if (!classCounts) {
          // Use Object.create(null) to prevent class assumptions involving property
          // names in Object.prototype
          classCounts = Object.create(null);
          element.data("$classCounts", classCounts);
        }

        if (name !== "ngClass") {
          if (!indexWatchExpression) {
            indexWatchExpression = $parse("$index", function moduloTwo($index) {
              return $index & 1;
            });
          }

          scope.$watch(indexWatchExpression, ngClassIndexWatchAction);
        }

        scope.$watch($parse(attr[name], toClassString), ngClassWatchAction);

        function addClasses(classString) {
          classString = digestClassCounts(split(classString), 1);
          if (hasAnimate(element[0])) {
            attr.$addClass(classString);
          } else {
            scope.$postUpdate(() => {
              if (classString !== "") {
                element[0].classList.add(...classString.trim().split(" "));
              }
            });
          }
        }

        function removeClasses(classString) {
          classString = digestClassCounts(split(classString), -1);
          if (hasAnimate(element[0])) {
            attr.$removeClass(classString);
          } else {
            scope.$postUpdate(() => {
              if (classString !== "") {
                element[0].classList.remove(...classString.trim().split(" "));
              }
            });
          }
        }

        function updateClasses(oldClassString, newClassString) {
          const oldClassArray = split(oldClassString);
          const newClassArray = split(newClassString);

          const toRemoveArray = arrayDifference(oldClassArray, newClassArray);
          const toAddArray = arrayDifference(newClassArray, oldClassArray);

          const toRemoveString = digestClassCounts(toRemoveArray, -1);
          const toAddString = digestClassCounts(toAddArray, 1);

          if (hasAnimate(element[0])) {
            attr.$addClass(toAddString);
            attr.$removeClass(toRemoveString);
          } else {
            scope.$postUpdate(() => {
              if (toAddString !== "") {
                element[0].classList.add(...toAddString.trim().split(" "));
              }
              if (toRemoveString !== "") {
                element[0].classList.remove(
                  ...toRemoveString.trim().split(" "),
                );
              }
            });
          }
        }

        function digestClassCounts(classArray, count) {
          const classesToUpdate = [];
          if (classArray) {
            classArray.forEach((className) => {
              if (count > 0 || classCounts[className]) {
                classCounts[className] = (classCounts[className] || 0) + count;
                if (classCounts[className] === +(count > 0)) {
                  classesToUpdate.push(className);
                }
              }
            });
          }
          return classesToUpdate.join(" ");
        }

        function ngClassIndexWatchAction(newModulo) {
          // This watch-action should run before the `ngClassWatchAction()`, thus it
          // adds/removes `oldClassString`. If the `ngClass` expression has changed as well, the
          // `ngClassWatchAction()` will update the classes.
          if (newModulo === selector) {
            addClasses(oldClassString);
          } else {
            removeClasses(oldClassString);
          }

          oldModulo = newModulo;
        }

        function ngClassWatchAction(newClassString) {
          if (oldModulo === selector) {
            updateClasses(oldClassString, newClassString);
          }

          oldClassString = newClassString;
        }
      },
    }),
  ];

  // Helpers
  function arrayDifference(tokens1, tokens2) {
    if (!tokens1 || !tokens1.length) return [];
    if (!tokens2 || !tokens2.length) return tokens1;

    const values = [];

    outer: for (let i = 0; i < tokens1.length; i++) {
      const token = tokens1[i];
      for (let j = 0; j < tokens2.length; j++) {
        if (token === tokens2[j]) continue outer;
      }
      values.push(token);
    }

    return values;
  }

  function split(classString) {
    return classString && classString.split(" ");
  }

  function toClassString(classValue) {
    if (!classValue) return classValue;

    let classString = classValue;

    if (Array.isArray(classValue)) {
      classString = classValue.map(toClassString).join(" ");
    } else if (isObject(classValue)) {
      classString = Object.keys(classValue)
        .filter((key) => classValue[key])
        .join(" ");
    } else if (!isString(classValue)) {
      classString = `${classValue}`;
    }

    return classString;
  }
}

export const ngClassDirective = classDirective("", true);
export const ngClassOddDirective = classDirective("Odd", 0);
export const ngClassEvenDirective = classDirective("Even", 1);




© 2015 - 2025 Weber Informatics LLC | Privacy Policy