# VStack

[🔰 CSS](../..) ⟩ [Layout](..) ⟩ [System]() ⟩\
🔸 [code](#code) ┊[問題](#problems)

{% tabs %}
{% tab title="📗 參考" %}
* Every Layout ⟩ [Stack](https://every-layout.dev/layouts/stack/) /⭐️
{% endtab %}

{% tab title="📘 手冊" %}
* SwiftUI ⟩ [VStack](https://developer.apple.com/documentation/swiftui/vstack)
* MDN ⟩&#x20;
  * HTMLElement ⟩ [dataset](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) (: [DOMStringMap](https://developer.mozilla.org/en-US/docs/Web/API/DOMStringMap)) - get/set "**data-xxx**" attributes ⭐️
  * Element ⟩&#x20;
    * [setAttribute()](https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute)
    * [getAttribute()](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute) ⟩ [Non-existing attributes](https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute#non-existing_attributes)
  * CSS ⟩&#x20;
    * [@import](https://developer.mozilla.org/en-US/docs/Web/CSS/@import) - import styles from other style sheets.
    * [Using data attributes](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) ("**data-xxx**" attributes) ⭐️
    * [attr()](https://developer.mozilla.org/en-US/docs/Web/CSS/attr\(\)) - use element's attributes in CSS ⭐️
    * [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
{% endtab %}

{% tab title="👥 相關" %}
* [Custom Attributes](../../../../browser/dom/type/element/attribute/dataset#dataset) - dataset
* [Box Model](../../box-model) ⟩ [height](../../box-model/height) - height: 100% problem
* [HStack](hstack), [Spacer](spacer)
* [flex](../display/flex/flex/flex)
{% endtab %}

{% tab title="🗣 討論" %}
* [Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry](https://stackoverflow.com/questions/54211849/uncaught-domexception-failed-to-execute-define-on-customelementregistry-th) - 這問題常出現在 CodeSandbox 上。
{% endtab %}
{% endtabs %}

{% hint style="info" %}
**用哪個好？ \<vstack->** 或 **\<i-vstack>** ❓(這兩個都是[**合法的名字**](../../../../component/custom-element#valid-name))\
💾 replit ⟩ [\<vstack-> valid name?](https://replit.com/@pegasusroe/lessvstack-greater-valid#index.html)
{% endhint %}

## version

{% tabs %}
{% tab title="v.0.0.5" %}
{% embed url="https://codesandbox.io/s/layout-v-0-0-5-box-2et3m?file=/layout/box.mjs" %}


{% endtab %}

{% tab title="vstack.mjs" %}
```javascript
// 0.0.5 + helpers

import {attr} from './helpers.mjs';

// VStack
export default class VStack extends HTMLElement {

    // this.render()
    render() {

        const {spacing, alignment} = this;
        const attrs = [spacing, alignment].filter(attr => !!attr);   // remove empty attributes

        // if no specific settings, use default values and return.
        if (attrs.length === 0) return;

        // style ID for this element
        const styleID = `VStack-${attrs.join('_')}`;

        // put vstack style ID in "data-xxx" attribute
        this.dataset.style = styleID;

        // if no such <style> with this ID, create it.
        if (!document.getElementById(styleID)) {

            // create <style>
            let style = document.createElement('style');
            style.id = styleID;

            // concate css rules
            const rules = [
                // spacing between items
                `${spacing ? `[data-style="${styleID}"] > * + :not(i-spacer) {margin-top: ${spacing};}` : ''}`,
                // stack alignmentment
                `${alignment ? `[data-style="${styleID}"] {align-items: ${alignment};}` : ''}`,
            ]
                .filter(rule => rule !== '')    // remove empty rules
                .join('\n');

            style.innerHTML = `${rules}`;
            document.head.appendChild(style);
        }
    }

    // -------------- getters/setters -------------------------

    // this.spacing (= '30px')
    get spacing() { return attr(this, 'spacing') }
    set spacing(val) { return attr(this, 'spacing', val) }

    // this.alignment (= 'flex-start' | 'center' | 'flex-end')
    get alignment() { return attr(this, 'alignment') }
    set alignment(val) { return attr(this, 'alignment', val) }

    // -------------- lifecycle callbacks -------------------------

    // ⭐ connected to document
    connectedCallback() {
        this.render();
    }

    // ⭐ observed attributes
    static get observedAttributes() {
        return ['spacing', 'alignment'];
    }

    // re-renders whenever one of attributes changes.
    attributeChangedCallback() {
        this.render();
    }
}

// ⭐️ register <vstack->
customElements.get('vstack-') || customElements.define('vstack-', VStack);

```
{% endtab %}

{% tab title="vstack.css" %}
```css
/* 0.1.0 - align-items: center -> stretch */

/* VStack */
vstack- {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

/* VStack 內非 spacer 的間隔 */
vstack- > * + :not(spacer-) {
  margin-top: var(--spacing);
}

/* 
    如果只有唯一一個 VStack，就讓它的高度為 100%，
    這樣裡面的 spacer 才能發生作用。

    ⭐️ 注意：
       此設定一定要「母物件」有「明確設定」height 才有效❗️
       母物件設定 min-height 沒有任何幫助喔❗️
 */
vstack-:only-child {
    height: 100%;
}
```
{% endtab %}

{% tab title="⬆️ 需要" %}
* [helper functions](../../../appendix/custom/helper-functions)
{% endtab %}

{% tab title="👥 相關" %}
* [Box](box), [HStack](hstack)
{% endtab %}

{% tab title="💾 程式" %}
* replit ⟩ [layout 0.1.0](https://replit.com/@pegasusroe/layout-010-refactor#layout/vstack.css) - align-items: center ➜ stretch
{% endtab %}
{% endtabs %}

## code lab

{% tabs %}
{% tab title="alignment" %}
{% embed url="https://codesandbox.io/s/vstack-alignment-ch6c3?file=/layout/vstack.mjs:2540-2574" %}
alignment
{% endembed %}
{% endtab %}

{% tab title="<spacer>" %}
{% embed url="https://codesandbox.io/s/spacer-g94ik" %}
HStack/VStack
{% endembed %}


{% endtab %}

{% tab title="CSS" %}
```css
/* ⭐️ HStack */

.hstack {
  display: flex;
  flex-direction: row;
}

/* ⭐️ VStack */

.vstack {
  display: flex;
  flex-direction: column;
}

/* ⭐️ Spacer */

.hstack > .spacer {
  margin-left: auto;
  width: 0;
  border: none;
}

.vstack > .spacer {
  margin-top: auto;
  height: 0;
  border: none;
}
```
{% endtab %}

{% tab title="<spacer>" %}
{% hint style="warning" %}
寫一個 \<spacer> 來代替 splitAfter❓(可行)
{% endhint %}

{% embed url="https://codepen.io/lochiwei/pen/QWgRvQV" %}
spacer(s) in HStack/VStack?
{% endembed %}


{% endtab %}
{% endtabs %}

## 問題 <a href="#problems" id="problems"></a>

{% tabs %}
{% tab title="align" %}
```markup
<i-vstack align="right"  class="container">
```

當在 custom element **\<i-vstack>** 中輸入屬性 **align="right"**，竟然會引發 browser 自動將它轉為：(🤔 **align="..."** 竟然對 [custom element](../../../component/custom-element) 也有效❓)

```css
i-vstack[Attributes Style] {
    text-align: right;
}
```

導致內部的所有子元素都繼承到此 **text-align** 性質：

![align="right"](https://2527454625-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MfvEFZnSBhKT6fJmus0%2F-MlA8KN3K3UQGamkIGOi%2F-MlA9av65baNENq0QiWp%2Falign%20right.png?alt=media\&token=ffce83f4-8cd2-4efd-a7d0-4db9c7c632a3)

{% hint style="warning" %}
HTML **align** attribute supports **col**, **colgroup**, **tbody**, **td**, **tfoot**, **th**, **thead**, **tr** elements. Usage of **align** attribute for **any other** HTML elements is **deprecated**. You must use CSS for those. 📗 w3resource ⟩ [HTML align attribute](https://www.w3resource.com/html/attributes/html-align-attribute.php)
{% endhint %}

{% hint style="info" %}
解決方案：\
嘗試將 **align**="..." 改為 **alignment**="..." 看看。(結果 OK ✅)
{% endhint %}
{% endtab %}

{% tab title=".define()" %}
* [Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry](https://stackoverflow.com/questions/54211849/uncaught-domexception-failed-to-execute-define-on-customelementregistry-th) - 這問題常出現在 CodeSandbox 上。

{% hint style="info" %}
解決方案：
{% endhint %}

```javascript
// ⭐️ register new tag <i-vstack>
customElements.get('i-vstack') || 
customElements.define('i-vstack', VStack);
```
{% endtab %}

{% tab title="height: 100%" %}
{% hint style="danger" %}
* In order for a **% value** to work for **height**, the **parent's height** must be **determined**. 🗣 [Why doesn't height: 100% working?](https://stackoverflow.com/a/7049918/5409815)
* 如果都是用**百分比**，則一路從 **html**  ⟩ **body** ⟩ ....  ⟩ 到自己的**直屬母物件**都要有明確設定 **%**，否則只要中間有一層斷掉也是沒用。
{% endhint %}

```css
/*
   當我們設定 height: 100% 時，此設定並不會真正發生作用，
   除非它的母物件有「明確設定」height。
   ⭐️ 注意：設定母物件的 min-height 並沒有幫助❗️
 */
i-vstack:only-child {
    height: 100%;
}
```
{% endtab %}

{% tab title="📗 參考" %}
* TutorialPublic ⟩ [How to set the height of a div to 100% using CSS](https://www.tutorialrepublic.com/faq/how-to-set-a-div-height-to-100-percent-using-css.php)
{% endtab %}

{% tab title="📘 手冊" %}
* MDN ⟩ CSS ⟩&#x20;
  * [text-align](https://developer.mozilla.org/en-US/docs/Web/CSS/text-align)
  * [align-items](https://developer.mozilla.org/en-US/docs/Web/CSS/align-items)
{% endtab %}

{% tab title="🗣 討論" %}
* [Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry](https://stackoverflow.com/questions/54211849/uncaught-domexception-failed-to-execute-define-on-customelementregistry-th) - 這問題常出現在 CodeSandbox 上。
{% endtab %}
{% endtabs %}
