Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Vue 3.0 #55

Open
StarkShang opened this issue Mar 11, 2021 · 10 comments
Open

Support for Vue 3.0 #55

StarkShang opened this issue Mar 11, 2021 · 10 comments

Comments

@StarkShang
Copy link

StarkShang commented Mar 11, 2021

Is there any plan to support for vue 3.0?

@ricardosimoes
Copy link

Any news on this?

@william-lyu
Copy link

Anyone can help?

@eladcandroid
Copy link

?

@b5710546232
Copy link

@pangaunn @voratham can you help ?

@eladcandroid
Copy link

@b5710546232

Try to use this as VueClamp.vue component file

<script>
import { addListener, removeListener } from "resize-detector";
import { defineComponent } from "vue";
import { h } from "vue";

export default defineComponent({
  name: "vue-clamp",
  props: {
    tag: {
      type: String,
      default: "div",
    },
    autoresize: {
      type: Boolean,
      default: false,
    },
    maxLines: Number,
    maxHeight: [String, Number],
    ellipsis: {
      type: String,
      default: "",
    },
    location: {
      type: String,
      default: "end",
      validator(value) {
        return ["start", "middle", "end"].indexOf(value) !== -1;
      },
    },
    expanded: Boolean,
  },
  data() {
    return {
      offset: null,
      text: this.getText(),
      localExpanded: !!this.expanded,
    };
  },
  computed: {
    clampedText() {
      if (this.location === "start") {
        return this.ellipsis + (this.text.slice(0, this.offset) || "").trim();
      } else if (this.location === "middle") {
        const split = Math.floor(this.offset / 2);
        return (
          (this.text.slice(0, split) || "").trim() +
          this.ellipsis +
          (this.text.slice(-split) || "").trim()
        );
      }

      return (this.text.slice(0, this.offset) || "").trim() + this.ellipsis;
    },
    isClamped() {
      if (!this.text) {
        return false;
      }
      return this.offset !== this.text.length;
    },
    realText() {
      return this.isClamped ? this.clampedText : this.text;
    },
    realMaxHeight() {
      if (this.localExpanded) {
        return null;
      }
      const { maxHeight } = this;
      if (!maxHeight) {
        return null;
      }
      return typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
    },
  },
  watch: {
    expanded(val) {
      this.localExpanded = val;
    },
    localExpanded(val) {
      if (val) {
        this.clampAt(this.text.length);
      } else {
        this.update();
      }
      if (this.expanded !== val) {
        this.$emit("update:expanded", val);
      }
    },
    isClamped: {
      handler(val) {
        this.$nextTick(() => this.$emit("clampchange", val));
      },
      immediate: true,
    },
  },
  mounted() {
    this.init();

    this.$watch(
      (vm) => [vm.maxLines, vm.maxHeight, vm.ellipsis, vm.isClamped].join(),
      this.update
    );
    this.$watch((vm) => [vm.tag, vm.text, vm.autoresize].join(), this.init);
  },
  updated() {
    this.text = this.getText();
    this.applyChange();
  },
  beforeUnmount() {
    this.cleanUp();
  },
  methods: {
    init() {
      const contents = this.$slots.default();

      if (!contents) {
        return;
      }

      this.offset = this.text.length;

      this.cleanUp();

      if (this.autoresize) {
        addListener(this.$el, this.update);
        this.unregisterResizeCallback = () => {
          removeListener(this.$el, this.update);
        };
      }
      this.update();
    },
    update() {
      if (this.localExpanded) {
        return;
      }
      this.applyChange();
      if (this.isOverflow() || this.isClamped) {
        this.search();
      }
    },
    expand() {
      this.localExpanded = true;
    },
    collapse() {
      this.localExpanded = false;
    },
    toggle() {
      this.localExpanded = !this.localExpanded;
    },
    getLines() {
      return Object.keys(
        Array.prototype.slice
          .call(this.$refs.content.getClientRects())
          .reduce((prev, { top, bottom }) => {
            const key = `${top}/${bottom}`;
            if (!prev[key]) {
              prev[key] = true;
            }
            return prev;
          }, {})
      ).length;
    },
    isOverflow() {
      if (!this.maxLines && !this.maxHeight) {
        return false;
      }

      if (this.maxLines) {
        if (this.getLines() > this.maxLines) {
          return true;
        }
      }

      if (this.maxHeight) {
        if (this.$el.scrollHeight > this.$el.offsetHeight) {
          return true;
        }
      }
      return false;
    },
    getText() {
      // Look for the first non-empty text node
      const [content] = (this.$slots.default() || []).filter(
        (node) => !node.tag && !node.isComment
      );
      return content ? content.children : "";
    },
    moveEdge(steps) {
      this.clampAt(this.offset + steps);
    },
    clampAt(offset) {
      this.offset = offset;
      this.applyChange();
    },
    applyChange() {
      this.$refs.text.textContent = this.realText;
    },
    stepToFit() {
      this.fill();
      this.clamp();
    },
    fill() {
      while (
        (!this.isOverflow() || this.getLines() < 2) &&
        this.offset < this.text.length
      ) {
        this.moveEdge(1);
      }
    },
    clamp() {
      while (this.isOverflow() && this.getLines() > 1 && this.offset > 0) {
        this.moveEdge(-1);
      }
    },
    search(...range) {
      const [from = 0, to = this.offset] = range;
      if (to - from <= 3) {
        this.stepToFit();
        return;
      }
      const target = Math.floor((to + from) / 2);
      this.clampAt(target);
      if (this.isOverflow()) {
        this.search(from, target);
      } else {
        this.search(target, to);
      }
    },
    cleanUp() {
      if (this.unregisterResizeCallback) {
        this.unregisterResizeCallback();
      }
    },
  },
  render() {
    const contents = [
      h(
        "span",
        {
          ref: "text",
          attrs: {
            "aria-label": this.text?.trim(),
          },
        },
        this.realText
      ),
    ];

    const { expand, collapse, toggle } = this;
    const scope = {
      expand,
      collapse,
      toggle,
      clamped: this.isClamped,
      expanded: this.localExpanded,
    };
    const before = this.$slots.before
      ? this.$slots.before(scope)
      : this.$slots.before;
    if (before) {
      contents.unshift(...(Array.isArray(before) ? before : [before]));
    }
    const after = this.$slots.after
      ? this.$slots.after(scope)
      : this.$slots.after;
    if (after) {
      contents.push(...(Array.isArray(after) ? after : [after]));
    }
    const lines = [
      h(
        "span",
        {
          style: {
            boxShadow: "transparent 0 0",
          },
          ref: "content",
        },
        contents
      ),
    ];
    return h(
      this.tag,
      {
        style: {
          maxHeight: this.realMaxHeight,
          overflow: "hidden",
        },
      },
      lines
    );
  },
});
</script>

@voratham
Copy link

voratham commented Mar 27, 2022

@eladcandroid Thx for your code since we would like to vue-clamp on vue3 typescript version for our project with @b5710546232
I have amount of time from weekend for change codebase to vue3 typescript, but i don't know about watch method value correct way for vue3 ? but i tested everything working correctly

https://gist.github.com/voratham/4f77d49182a82d6ea84fb244fde857e1

@kenny-lee-1992
Copy link

@eladcandroid Thx for your code since we would like to vue-clamp on vue3 typescript version for our project with @b5710546232 I have amount of time from weekend for change codebase to vue3 typescript, but i don't know about watch method value correct way for vue3 ? but i tested everything working correctly

https://gist.github.com/voratham/4f77d49182a82d6ea84fb244fde857e1

When the text change the clamp has been not updated. So i come back to use the scrip from eladcandroid and it works.

Thank you both,

@sherwinshen
Copy link

vue3 version here 👉 https://github.com/sherwinshen/vue3-text-clamp

@chengruolan
Copy link

When can vue3 be supported

@d1y
Copy link

d1y commented Jun 2, 2023

vue3 version here 👉 https://github.com/sherwinshen/vue3-text-clamp

Thank you~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants