sd-render 1.2.4 → 1.2.6

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.
Files changed (110) hide show
  1. package/field-docs.html +573 -10
  2. package/package.json +1 -1
  3. package/{sd-lib-Cczl-l04.js → sd-lib-BG0UjdFl.js} +6451 -5011
  4. package/{sd-render-DETM6GzA.js → sd-render-DwCKOhLI.js} +1646 -1353
  5. package/sd-render.es.js +61 -58
  6. package/sd-render.style.css +1 -1
  7. package/types/src/components/form-render/SdFormRender.vue.d.ts +3 -0
  8. package/types/src/components/form-render/form-container/affix-render.vue.d.ts +7 -1
  9. package/types/src/components/form-render/form-field/alert-ui.vue.d.ts +1 -0
  10. package/types/src/components/form-render/form-field/apexchart-ui.vue.d.ts +1 -0
  11. package/types/src/components/form-render/form-field/autonumber-input.vue.d.ts +1 -0
  12. package/types/src/components/form-render/form-field/avatar-ui.vue.d.ts +1 -0
  13. package/types/src/components/form-render/form-field/btn-editor-input.vue.d.ts +1 -0
  14. package/types/src/components/form-render/form-field/button-ui.vue.d.ts +1 -0
  15. package/types/src/components/form-render/form-field/carousel-ui.vue.d.ts +1 -0
  16. package/types/src/components/form-render/form-field/cascader-form-input.vue.d.ts +1 -0
  17. package/types/src/components/form-render/form-field/chart-ui.vue.d.ts +1 -0
  18. package/types/src/components/form-render/form-field/checkbox-input.vue.d.ts +1 -0
  19. package/types/src/components/form-render/form-field/code-input.vue.d.ts +1 -0
  20. package/types/src/components/form-render/form-field/color-input.vue.d.ts +1 -0
  21. package/types/src/components/form-render/form-field/crop-upload-input.vue.d.ts +1 -0
  22. package/types/src/components/form-render/form-field/datagrid-form-ui.vue.d.ts +1 -0
  23. package/types/src/components/form-render/form-field/datagrid-sql-ui.vue.d.ts +1 -0
  24. package/types/src/components/form-render/form-field/date-input.vue.d.ts +556 -0
  25. package/types/src/components/form-render/form-field/date-range-input.vue.d.ts +556 -0
  26. package/types/src/components/form-render/form-field/divider-ui.vue.d.ts +1 -0
  27. package/types/src/components/form-render/form-field/dropdown-ui.vue.d.ts +1 -0
  28. package/types/src/components/form-render/form-field/dynamic-input.vue.d.ts +1 -0
  29. package/types/src/components/form-render/form-field/file-upload-input.vue.d.ts +1 -0
  30. package/types/src/components/form-render/form-field/group-list-input.vue.d.ts +1 -0
  31. package/types/src/components/form-render/form-field/html-input.vue.d.ts +1 -0
  32. package/types/src/components/form-render/form-field/html-ui.vue.d.ts +1 -0
  33. package/types/src/components/form-render/form-field/icon-input.vue.d.ts +1 -0
  34. package/types/src/components/form-render/form-field/image-ui.vue.d.ts +1 -0
  35. package/types/src/components/form-render/form-field/json-input.vue.d.ts +1 -0
  36. package/types/src/components/form-render/form-field/link-ui.vue.d.ts +1 -0
  37. package/types/src/components/form-render/form-field/list-ui.vue.d.ts +3 -0
  38. package/types/src/components/form-render/form-field/masked-input.vue.d.ts +1 -0
  39. package/types/src/components/form-render/form-field/multiple-date.vue.d.ts +556 -0
  40. package/types/src/components/form-render/form-field/number-input.vue.d.ts +1 -0
  41. package/types/src/components/form-render/form-field/objectid-input.vue.d.ts +1 -0
  42. package/types/src/components/form-render/form-field/picture-upload-input.vue.d.ts +1 -0
  43. package/types/src/components/form-render/form-field/progress-ui.vue.d.ts +1 -0
  44. package/types/src/components/form-render/form-field/qrcode-ui.vue.d.ts +1 -0
  45. package/types/src/components/form-render/form-field/radio-input.vue.d.ts +1 -0
  46. package/types/src/components/form-render/form-field/radio-text-input.vue.d.ts +1 -0
  47. package/types/src/components/form-render/form-field/rate-input.vue.d.ts +1 -0
  48. package/types/src/components/form-render/form-field/record-ui.vue.d.ts +3 -0
  49. package/types/src/components/form-render/form-field/report-ui.vue.d.ts +1 -0
  50. package/types/src/components/form-render/form-field/scan-code-ui.vue.d.ts +624 -0
  51. package/types/src/components/form-render/form-field/segmented-ui.vue.d.ts +1 -0
  52. package/types/src/components/form-render/form-field/select-data-input.vue.d.ts +1 -0
  53. package/types/src/components/form-render/form-field/select-form-input.vue.d.ts +1 -0
  54. package/types/src/components/form-render/form-field/select-input.vue.d.ts +1 -0
  55. package/types/src/components/form-render/form-field/select-path-input.vue.d.ts +1 -0
  56. package/types/src/components/form-render/form-field/select-sql-input.vue.d.ts +1 -0
  57. package/types/src/components/form-render/form-field/side-menu-ui.vue.d.ts +1 -0
  58. package/types/src/components/form-render/form-field/slider-input.vue.d.ts +1 -0
  59. package/types/src/components/form-render/form-field/statistic-ui.vue.d.ts +1 -0
  60. package/types/src/components/form-render/form-field/step-ui.vue.d.ts +1 -0
  61. package/types/src/components/form-render/form-field/svg-input.vue.d.ts +1 -0
  62. package/types/src/components/form-render/form-field/svg-ui.vue.d.ts +1 -0
  63. package/types/src/components/form-render/form-field/switch-input.vue.d.ts +1 -0
  64. package/types/src/components/form-render/form-field/tags-input.vue.d.ts +1 -0
  65. package/types/src/components/form-render/form-field/text-input.vue.d.ts +1 -0
  66. package/types/src/components/form-render/form-field/text-ui.vue.d.ts +1 -0
  67. package/types/src/components/form-render/form-field/textarea-input.vue.d.ts +1 -0
  68. package/types/src/components/form-render/form-field/time-input.vue.d.ts +1 -0
  69. package/types/src/components/form-render/form-field/time-range-input.vue.d.ts +1 -0
  70. package/types/src/components/form-render/form-field/time-select-input.vue.d.ts +1 -0
  71. package/types/src/components/form-render/form-field/tour-ui.vue.d.ts +1 -0
  72. package/types/src/components/form-render/form-field/vue-ui.vue.d.ts +454 -0
  73. package/types/src/components/form-render/mixins/CoreFieldMixin.d.ts +1 -0
  74. package/types/src/components/input3/SdCropImageInput.vue.d.ts +1 -1
  75. package/types/src/components/input3/SdHtmlEditor.vue.d.ts +5 -5
  76. package/types/src/components/input3/SdMaskedInput.vue.d.ts +2 -2
  77. package/types/src/components/input3/eltiptap/hooks/useCodeView.d.ts +7 -1
  78. package/types/src/components/input3/eltiptap/widget/ElementTiptap.vue.d.ts +5 -5
  79. package/types/src/components/input3/eltiptap/widget/MenuCommands/CommandButton.vue.d.ts +1 -1
  80. package/types/src/components/sdlib.d.ts +4 -1
  81. package/types/src/components/sdwidget/SDCustomContent.vue.d.ts +96 -0
  82. package/types/src/components/sdwidget/SDImportAndModified.vue.d.ts +1 -1
  83. package/types/src/components/sdwidget/SDImportData.vue.d.ts +1 -1
  84. package/types/src/components/sdwidget/SDImportMapData.vue.d.ts +1 -1
  85. package/types/src/components/sdwidget/SdCascaderForm.vue.d.ts +1 -1
  86. package/types/src/components/sdwidget/SdChart.vue.d.ts +282 -0
  87. package/types/src/components/sdwidget/SdCodeMirror.vue.d.ts +232 -0
  88. package/types/src/components/sdwidget/SdCrudForm.vue.d.ts +5 -1
  89. package/types/src/components/sdwidget/SdCrudGrid.vue.d.ts +2 -1
  90. package/types/src/components/sdwidget/SdCrudPopupForm.vue.d.ts +2 -2
  91. package/types/src/components/sdwidget/SdCrudPopupGrid.vue.d.ts +2 -2
  92. package/types/src/components/sdwidget/SdCrudSelect.vue.d.ts +2 -2
  93. package/types/src/components/sdwidget/SdCrudSelectSubForm.vue.d.ts +3 -2
  94. package/types/src/components/sdwidget/SdDataDicPopup.vue.d.ts +1 -1
  95. package/types/src/components/sdwidget/SdDatePickerBE.vue.d.ts +987 -0
  96. package/types/src/components/sdwidget/SdDynamicInput.vue.d.ts +4 -4
  97. package/types/src/components/sdwidget/SdExportData.vue.d.ts +1 -1
  98. package/types/src/components/sdwidget/SdFormErd.vue.d.ts +2 -2
  99. package/types/src/components/sdwidget/SdGrid.vue.d.ts +1 -1
  100. package/types/src/components/sdwidget/SdIcon.vue.d.ts +2 -2
  101. package/types/src/components/sdwidget/SdOptionsItems.vue.d.ts +1 -1
  102. package/types/src/components/sdwidget/SdScan.vue.d.ts +369 -0
  103. package/types/src/components/sdwidget/SdSelectPathData.vue.d.ts +2 -2
  104. package/types/src/components/sdwidget/SdSelectRemoteList.vue.d.ts +2 -2
  105. package/types/src/components/sdwidget/SdUiListView.vue.d.ts +15 -0
  106. package/types/src/components/sdwidget/SdUiMenu.vue.d.ts +2 -2
  107. package/types/src/components/sdwidget/SdUiRecordView.vue.d.ts +159 -1
  108. package/types/src/env.d.ts +3 -0
  109. package/types/src/types/SdForm.d.ts +1 -0
  110. package/types/src/components/input3/SdAceEditor.vue.d.ts +0 -233
package/field-docs.html CHANGED
@@ -142,9 +142,10 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
142
142
  <a href="#icon-input">Icon List</a>
143
143
  </div>
144
144
  <div class="group">
145
- <div class="group-title">Display UI (25)</div>
145
+ <div class="group-title">Display UI (26)</div>
146
146
  <a href="#text-ui">Static Text</a>
147
147
  <a href="#html-ui">Content</a>
148
+ <a href="#vue-ui">Components</a>
148
149
  <a href="#link-ui">Link Text</a>
149
150
  <a href="#divider-ui">Divider</a>
150
151
  <a href="#progress-ui">Progress Bar</a>
@@ -186,10 +187,15 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
186
187
  <a href="#sub-form">Sub Form</a>
187
188
  <a href="#object-group">Object Group</a>
188
189
  </div>
190
+ <div class="group">
191
+ <div class="group-title">Special Widget (1)</div>
192
+ <a href="#sd-custom-content">SD Custom Content</a>
193
+ </div>
189
194
  <div class="group">
190
195
  <div class="group-title">Functions</div>
191
196
  <a href="#field-functions">Field Functions</a>
192
197
  <a href="#form-functions">Form Functions</a>
198
+ <a href="#popup-form-guide">Popup Form Guide</a>
193
199
  <a href="#api-functions">API Functions</a>
194
200
  <a href="#variables">Variables</a>
195
201
  </div>
@@ -8679,6 +8685,145 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
8679
8685
  </table>
8680
8686
  </div>
8681
8687
  </div>
8688
+ <div class="card" id="vue-ui">
8689
+ <div class="card-head">
8690
+ <h3>Components</h3>
8691
+ <div class="tags">
8692
+
8693
+
8694
+
8695
+ </div>
8696
+ </div>
8697
+ <div class="tab-bar">
8698
+ <button class="tab-btn active" onclick="showTab(this,0)">COMMON <span class="tab-count">5</span></button>
8699
+ <button class="tab-btn" onclick="showTab(this,1)">ADVANCED <span class="tab-count">0</span></button>
8700
+ <button class="tab-btn" onclick="showTab(this,2)">EVENTS <span class="tab-count">2</span></button>
8701
+ </div>
8702
+ <div class="tab-panel active" data-idx="0">
8703
+ <table>
8704
+ <tr><th>Option</th><th>Label</th><th>Default</th><th>Description</th></tr>
8705
+ <tr>
8706
+ <td class="opt-key">name</td>
8707
+ <td class="opt-label">Variable Name</td>
8708
+ <td><span class="v-empty">''</span></td>
8709
+ <td class="opt-hint"></td>
8710
+ </tr>
8711
+ <tr>
8712
+ <td class="opt-key">columnSpan</td>
8713
+ <td class="opt-label">Column Span</td>
8714
+ <td><span class="v-num">4</span></td>
8715
+ <td class="opt-hint"></td>
8716
+ </tr>
8717
+ <tr>
8718
+ <td class="opt-key">hidden</td>
8719
+ <td class="opt-label">Hide</td>
8720
+ <td><span class="v-bool">false</span></td>
8721
+ <td class="opt-hint"></td>
8722
+ </tr>
8723
+ <tr>
8724
+ <td class="opt-key">content</td>
8725
+ <td class="opt-label">Template</td>
8726
+ <td><span class="v-str">'&lt;el-alert title=&quot;Vue UI&quot; type=&quot;info&quot; :closable=&quot;false&quot; /&gt;'</span></td>
8727
+ <td class="opt-hint">Vue Template (el-*, {{data}}, v-if/v-for)</td>
8728
+ </tr>
8729
+ <tr>
8730
+ <td class="opt-key">customClass</td>
8731
+ <td class="opt-label">Custom Class</td>
8732
+ <td><span class="v-empty">''</span></td>
8733
+ <td class="opt-hint"></td>
8734
+ </tr>
8735
+ </table>
8736
+ </div>
8737
+ <div class="tab-panel" data-idx="1">
8738
+ <table>
8739
+ <tr><th>Option</th><th>Label</th><th>Default</th><th>Description</th></tr>
8740
+ </table>
8741
+ </div>
8742
+ <div class="tab-panel" data-idx="2">
8743
+ <table>
8744
+ <tr><th>Event</th><th>Label</th><th>Parameters</th><th>Description</th></tr>
8745
+ <tr>
8746
+ <td class="opt-key">onCreated</td>
8747
+ <td class="opt-label">onCreated</td>
8748
+ <td class="opt-params"></td>
8749
+ <td class="opt-hint"></td>
8750
+ </tr>
8751
+ <tr>
8752
+ <td class="opt-key">onMounted</td>
8753
+ <td class="opt-label">onMounted</td>
8754
+ <td class="opt-params"></td>
8755
+ <td class="opt-hint"></td>
8756
+ </tr>
8757
+ </table>
8758
+ </div>
8759
+
8760
+ <div style="padding: 12px 14px; border-top: 1px solid #232336;">
8761
+ <div class="example-label" style="font-size: 12px; color: #89b4fa; margin-bottom: 4px;">📘 ตัวอย่างการใช้งาน — vueState (zone เขียนสคริปประจำ widget)</div>
8762
+ <div style="color: #585b70; font-size: 11px; margin-bottom: 12px;">
8763
+ template เข้าถึง 3 scope: <code>params</code> (context/system) &gt; <code>vueState</code> (zone ของ widget) &gt; <code>data</code> (form model).
8764
+ เก็บ data/function/instance ใน <code>vueState</code> แล้ว push/assign ได้ — UI re-render เอง · ไม่ถูก submit
8765
+ </div>
8766
+
8767
+ <div class="fn-item">
8768
+ <div class="fn-head">
8769
+ <span class="fn-name">1. el-tree + ปุ่มเพิ่ม node</span>
8770
+ <span class="fn-returns">reactive list</span>
8771
+ </div>
8772
+ <div class="fn-desc">ใส่ default ใน <code>onCreated</code> (พร้อมก่อน render) · field อื่นแก้ผ่าน <code>refField</code></div>
8773
+ <div class="fn-example">
8774
+ <div class="example-label">vue_ui · content (template)</div>
8775
+ <pre><code>&lt;el-tree style="max-width: 600px" :data="items" /&gt;</code></pre>
8776
+ <div class="example-label" style="margin-top: 8px;">vue_ui · onCreated</div>
8777
+ <pre><code>this.vueState.items = [
8778
+ { label: 'Level one 1', children: [{ label: 'Level two 1-1', children: [] }] }
8779
+ ]</code></pre>
8780
+ <div class="example-label" style="margin-top: 8px;">button · onClick (ชื่อ vue_ui ต้องตรง options.name)</div>
8781
+ <pre><code>this.refField('vue_ui').vueState.items.push({ label: 'Level one 2', children: [] })
8782
+ // → reactive, el-tree อัปเดตทันที</code></pre>
8783
+ </div>
8784
+ </div>
8785
+
8786
+ <div class="fn-item">
8787
+ <div class="fn-head">
8788
+ <span class="fn-name">2. เก็บ function ในโซน</span>
8789
+ <span class="fn-returns">arrow fn</span>
8790
+ </div>
8791
+ <div class="fn-desc">เก็บ method ใน <code>vueState</code> แล้วเรียกจาก <code>@click</code> ได้ตรงๆ (ใช้ arrow → closure อ้าง <code>s</code>)</div>
8792
+ <div class="fn-example">
8793
+ <div class="example-label">vue_ui · onCreated</div>
8794
+ <pre><code>const s = this.vueState
8795
+ s.count = 0
8796
+ s.add = (label) =&gt; { s.items.push({ label }); s.count++ }</code></pre>
8797
+ <div class="example-label" style="margin-top: 8px;">vue_ui · content</div>
8798
+ <pre><code>&lt;el-button @click="add('node ' + (count + 1))"&gt;เพิ่ม ({{ count }})&lt;/el-button&gt;
8799
+ &lt;el-tree :data="items" /&gt;</code></pre>
8800
+ </div>
8801
+ </div>
8802
+
8803
+ <div class="fn-item">
8804
+ <div class="fn-head">
8805
+ <span class="fn-name">⚠️ ข้อควรระวัง</span>
8806
+ <span class="fn-returns" style="background: #3a2f1e; color: #fab387;">กันพลาด</span>
8807
+ </div>
8808
+ <div class="fn-example">
8809
+ <pre><code>// ❌ key ห้ามขึ้นต้น _ หรือ $ → template มองไม่เห็น
8810
+ s._tmp = 1 // ใช้ s.tmp แทน
8811
+
8812
+ // ❌ ชื่ออย่าชนกับ params (params ชนะ → ค่า zone โดนบัง)
8813
+ s.userInfo = {...} // ชนระบบ — ใช้ชื่อเฉพาะตัว เช่น s.treeData
8814
+
8815
+ // ❌ instance ต้อง markRaw (กัน deep reactive wrap จนเพี้ยน/ช้า)
8816
+ s.chart = markRaw(echarts.init(el))
8817
+
8818
+ // ❌ vueState ไม่ persist — รีโหลดฟอร์มหาย
8819
+ // ถ้าต้องเก็บค่า sync เข้า data.xxx (form model) หรือยิง API
8820
+
8821
+ // ❌ ส่ง vueState เป็น primitive จะขึ้น error box แจ้ง designer
8822
+ this.vueState = 'x' // ต้องเป็น object เสมอ — ใช้ s.key = ... แทน</code></pre>
8823
+ </div>
8824
+ </div>
8825
+ </div>
8826
+ </div>
8682
8827
  <div class="card" id="link-ui">
8683
8828
  <div class="card-head">
8684
8829
  <h3>Link Text</h3>
@@ -10454,7 +10599,7 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
10454
10599
  </div>
10455
10600
  <div class="tab-bar">
10456
10601
  <button class="tab-btn active" onclick="showTab(this,0)">COMMON <span class="tab-count">23</span></button>
10457
- <button class="tab-btn" onclick="showTab(this,1)">ADVANCED <span class="tab-count">13</span></button>
10602
+ <button class="tab-btn" onclick="showTab(this,1)">ADVANCED <span class="tab-count">18</span></button>
10458
10603
  <button class="tab-btn" onclick="showTab(this,2)">EVENTS <span class="tab-count">8</span></button>
10459
10604
  </div>
10460
10605
  <div class="tab-panel active" data-idx="0">
@@ -10604,6 +10749,36 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
10604
10749
  <table>
10605
10750
  <tr><th>Option</th><th>Label</th><th>Default</th><th>Description</th></tr>
10606
10751
  <tr>
10752
+ <td class="opt-key">customContentEnable</td>
10753
+ <td class="opt-label">Custom Content</td>
10754
+ <td><span class="v-bool">false</span></td>
10755
+ <td class="opt-hint">render ผ่าน SDCustomContent แทน Display Fields</td>
10756
+ </tr>
10757
+ <tr>
10758
+ <td class="opt-key">customContent</td>
10759
+ <td class="opt-label">Content Temp</td>
10760
+ <td><span class="v-empty">''</span></td>
10761
+ <td class="opt-hint">Vue Template — {{field}}, fmt('field'), can.* / actions.*</td>
10762
+ </tr>
10763
+ <tr>
10764
+ <td class="opt-key">autoActionFooter</td>
10765
+ <td class="opt-label">Auto Action Footer</td>
10766
+ <td><span class="v-bool">true</span></td>
10767
+ <td class="opt-hint">widget render ปุ่ม action footer ให้ · ปิด = เขียนเองใน Content Temp ผ่าน can/actions</td>
10768
+ </tr>
10769
+ <tr>
10770
+ <td class="opt-key">metaByField</td>
10771
+ <td class="opt-label">Footer By Field</td>
10772
+ <td><span class="v-empty">''</span></td>
10773
+ <td class="opt-hint"></td>
10774
+ </tr>
10775
+ <tr>
10776
+ <td class="opt-key">metaTimeField</td>
10777
+ <td class="opt-label">Footer Time Field</td>
10778
+ <td><span class="v-empty">''</span></td>
10779
+ <td class="opt-hint"></td>
10780
+ </tr>
10781
+ <tr>
10607
10782
  <td class="opt-key">enableWs</td>
10608
10783
  <td class="opt-label">WebSocket Enable</td>
10609
10784
  <td><span class="v-bool">true</span></td>
@@ -13673,6 +13848,226 @@ td code { background: #313244; padding: 1px 6px; border-radius: 3px; font-size:
13673
13848
  </div>
13674
13849
  </section>
13675
13850
 
13851
+ <section class="section" id="sec-special-widget">
13852
+ <h2>Special Widget <span class="count">advanced / dev only</span></h2>
13853
+
13854
+ <div class="card" id="sd-custom-content">
13855
+ <div class="card-head">
13856
+ <h3>SD Custom Content</h3>
13857
+ <div class="tags">
13858
+ <span class="tag tag-type">DisplayUi</span>
13859
+ <span class="tag tag-pro">pro</span>
13860
+ <span class="tag tag-pro">dev only</span>
13861
+ </div>
13862
+ </div>
13863
+ <div class="fn-desc">
13864
+ Render <b>HTML / Vue template string</b> อย่างปลอดภัย — รองรับ HTML tag ปกติ, component ของ Element Plus (<code>el-*</code>) + widget,
13865
+ directive (<code>v-if</code> / <code>v-for</code> / <code>v-show</code> / <code>:</code> / <code>@</code>) และ interpolation <code>{{ varname }}</code> จาก prop <code>data</code> / <code>params</code>.
13866
+ <br>เทคนิค: Vue runtime compiler (lazy import) + DOMPurify sanitize + cache render function.
13867
+ </div>
13868
+ <div class="tab-bar">
13869
+ <button class="tab-btn active" onclick="showTab(this,0)">PROPS <span class="tab-count">5</span></button>
13870
+ <button class="tab-btn" onclick="showTab(this,1)">HELPERS <span class="tab-count">4</span></button>
13871
+ <button class="tab-btn" onclick="showTab(this,2)">EVENTS <span class="tab-count">1</span></button>
13872
+ </div>
13873
+ <div class="tab-panel active" data-idx="0">
13874
+ <table>
13875
+ <tr><th>Prop</th><th>Type</th><th>Default</th><th>Description</th></tr>
13876
+ <tr>
13877
+ <td class="opt-key">content</td>
13878
+ <td class="param-type">string</td>
13879
+ <td><span class="v-empty">''</span></td>
13880
+ <td class="opt-hint">HTML / Vue template string ที่จะ render</td>
13881
+ </tr>
13882
+ <tr>
13883
+ <td class="opt-key">data</td>
13884
+ <td class="param-type">object</td>
13885
+ <td><span class="v-str">{}</span></td>
13886
+ <td class="opt-hint">ตัวแปรสำหรับ binding — {{ name }}, v-for="i in list", v-if="ok"</td>
13887
+ </tr>
13888
+ <tr>
13889
+ <td class="opt-key">params</td>
13890
+ <td class="param-type">object</td>
13891
+ <td><span class="v-str">{}</span></td>
13892
+ <td class="opt-hint">context เพิ่มเติม (override data เมื่อ key ชนกัน) เช่น params จากฟอร์ม</td>
13893
+ </tr>
13894
+ <tr>
13895
+ <td class="opt-key">components</td>
13896
+ <td class="param-type">object</td>
13897
+ <td><span class="v-str">{}</span></td>
13898
+ <td class="opt-hint">widget เพิ่มเติมที่ไม่ได้ register global (el-* ไม่ต้องส่ง)</td>
13899
+ </tr>
13900
+ <tr>
13901
+ <td class="opt-key">sanitize</td>
13902
+ <td class="param-type">boolean</td>
13903
+ <td><span class="v-bool">true</span></td>
13904
+ <td class="opt-hint">เปิด DOMPurify sanitize (กรอง &lt;script&gt;, onerror=, javascript:)</td>
13905
+ </tr>
13906
+ </table>
13907
+ </div>
13908
+ <div class="tab-panel" data-idx="1">
13909
+ <table>
13910
+ <tr><th>Method</th><th>Returns</th><th>Parameter</th><th>Description</th></tr>
13911
+ <tr>
13912
+ <td class="opt-key">getRef</td>
13913
+ <td class="param-type">instance | element</td>
13914
+ <td class="opt-params">name: string</td>
13915
+ <td class="opt-hint">เข้าถึงสิ่งที่เขียน ref="name" ใน template (widget instance หรือ DOM element)</td>
13916
+ </tr>
13917
+ <tr>
13918
+ <td class="opt-key">getEl</td>
13919
+ <td class="param-type">HTMLElement</td>
13920
+ <td class="opt-params">name: string</td>
13921
+ <td class="opt-hint">DOM element ของ ref — component คืน $el, tag ปกติคืน element</td>
13922
+ </tr>
13923
+ <tr>
13924
+ <td class="opt-key">getRefs</td>
13925
+ <td class="param-type">object</td>
13926
+ <td class="opt-params"></td>
13927
+ <td class="opt-hint">ref ทั้งหมดเป็น object { name: ref }</td>
13928
+ </tr>
13929
+ <tr>
13930
+ <td class="opt-key">getInner</td>
13931
+ <td class="param-type">instance</td>
13932
+ <td class="opt-params"></td>
13933
+ <td class="opt-hint">inner component instance (low-level เผื่อต้องเข้าถึงลึก)</td>
13934
+ </tr>
13935
+ </table>
13936
+ </div>
13937
+ <div class="tab-panel" data-idx="2">
13938
+ <table>
13939
+ <tr><th>Event</th><th>Parameters</th><th>Description</th></tr>
13940
+ <tr>
13941
+ <td class="opt-key">rendered</td>
13942
+ <td class="opt-params">refs: object</td>
13943
+ <td class="opt-hint">emit หลัง render เสร็จ + DOM mount แล้ว (ref พร้อมใช้) ส่ง refs มาด้วย</td>
13944
+ </tr>
13945
+ </table>
13946
+ </div>
13947
+ </div>
13948
+
13949
+ <div class="fn-list">
13950
+
13951
+ <div class="fn-item">
13952
+ <div class="fn-head">
13953
+ <span class="fn-name">1. การใช้งานพื้นฐาน</span>
13954
+ <span class="fn-returns">interpolation + el- + v-if</span>
13955
+ </div>
13956
+ <div class="fn-desc">ส่ง template เข้า <code>content</code> และตัวแปรเข้า <code>data</code> — {{ }} แสดงค่าแบบ escape เป็น text (ปลอดภัย)</div>
13957
+ <div class="fn-example">
13958
+ <div class="example-label">Host component</div>
13959
+ <pre><code>&lt;SDCustomContent :content="tpl" :data="vars" /&gt;
13960
+
13961
+ // ใน data() ของ host
13962
+ tpl: `&lt;div&gt;
13963
+ &lt;h3&gt;สวัสดี {{ user.name }}&lt;/h3&gt;
13964
+ &lt;el-tag :type="user.vip ? 'success' : 'info'"&gt;
13965
+ {{ user.vip ? 'VIP' : 'ทั่วไป' }}
13966
+ &lt;/el-tag&gt;
13967
+ &lt;p v-if="user.vip"&gt;สมาชิก VIP&lt;/p&gt;
13968
+ &lt;/div&gt;`,
13969
+ vars: { user: { name: 'พี่', vip: true } }</code></pre>
13970
+ </div>
13971
+ </div>
13972
+
13973
+ <div class="fn-item">
13974
+ <div class="fn-head">
13975
+ <span class="fn-name">2. v-for แสดง array</span>
13976
+ <span class="fn-returns">el-table / list</span>
13977
+ </div>
13978
+ <div class="fn-desc">วน array จาก data ด้วย v-for — เปลี่ยน array แล้ว re-render อัตโนมัติ</div>
13979
+ <div class="fn-example">
13980
+ <div class="example-label">Template</div>
13981
+ <pre><code>&lt;el-table :data="rows" size="small" border&gt;
13982
+ &lt;el-table-column prop="name" label="สินค้า" /&gt;
13983
+ &lt;el-table-column prop="price" label="ราคา" width="120" /&gt;
13984
+ &lt;/el-table&gt;
13985
+
13986
+ // data
13987
+ rows: [
13988
+ { name: 'ปูนซีเมนต์', price: 175 },
13989
+ { name: 'เหล็กเส้น', price: 240 },
13990
+ ]</code></pre>
13991
+ </div>
13992
+ </div>
13993
+
13994
+ <div class="fn-item">
13995
+ <div class="fn-head">
13996
+ <span class="fn-name">3. Event handler (@click)</span>
13997
+ <span class="fn-returns">method ใน data</span>
13998
+ </div>
13999
+ <div class="fn-desc">ใส่ <b>ฟังก์ชัน</b>ใน data แล้ว template เรียกผ่าน @click / @change ได้เลย</div>
14000
+ <div class="fn-example">
14001
+ <div class="example-label">Template + data</div>
14002
+ <pre><code>&lt;el-button @click="greet(user.name)"&gt;ทักทาย&lt;/el-button&gt;
14003
+ &lt;el-button @click="addItem"&gt;เพิ่ม ({{ list.length }})&lt;/el-button&gt;
14004
+
14005
+ // data
14006
+ {
14007
+ user: { name: 'พี่' },
14008
+ list: myReactiveList,
14009
+ greet: (name) =&gt; ElMessage.success('สวัสดี ' + name),
14010
+ addItem: () =&gt; myReactiveList.push('item'),
14011
+ }
14012
+
14013
+ // ⚠️ inline mutation ไม่ persist: @click="counter++" เขียนกลับ data ไม่ได้
14014
+ // ต้องผ่าน method เช่น @click="inc()" โดย inc: () =&gt; data.counter++</code></pre>
14015
+ </div>
14016
+ </div>
14017
+
14018
+ <div class="fn-item">
14019
+ <div class="fn-head">
14020
+ <span class="fn-name">4. Ref helper</span>
14021
+ <span class="fn-returns">เข้าถึง widget ภายใน</span>
14022
+ </div>
14023
+ <div class="fn-desc">เขียน ref="name" ใน template แล้ว host เข้าถึงผ่าน getRef — ใช้ได้หลัง render เสร็จ (ดัก @rendered หรือรอ $nextTick)</div>
14024
+ <div class="fn-example">
14025
+ <div class="example-label">Host</div>
14026
+ <pre><code>&lt;SDCustomContent ref="cc" :content="tpl" @rendered="onReady" /&gt;
14027
+
14028
+ // template มี: &lt;el-input ref="nameInput" /&gt;
14029
+
14030
+ methods: {
14031
+ onReady(refs) {
14032
+ // refs = { nameInput: ElInputInstance }
14033
+ },
14034
+ focusInput() {
14035
+ this.$refs.cc.getRef('nameInput')?.focus()
14036
+ // getEl('nameInput') → DOM element
14037
+ // getRefs() → ref ทั้งหมด
14038
+ }
14039
+ }</code></pre>
14040
+ </div>
14041
+ </div>
14042
+
14043
+ <div class="fn-item">
14044
+ <div class="fn-head">
14045
+ <span class="fn-name">⚠️ Security</span>
14046
+ <span class="fn-returns">ต้องอ่าน</span>
14047
+ </div>
14048
+ <div class="fn-desc">
14049
+ template = รัน JavaScript จริง (compile ด้วย runtime compiler) — expression เข้าถึง global ได้ (window, document, fetch)
14050
+ DOMPurify กรองได้แค่โครง HTML <b>ไม่ได้</b> sandbox JS
14051
+ </div>
14052
+ <div class="fn-example">
14053
+ <div class="example-label">กฎความปลอดภัย</div>
14054
+ <pre><code>// ✅ ปลอดภัย: content มาจาก dev / form schema เท่านั้น
14055
+ // (designer เขียน JS ได้อยู่แล้วผ่าน field events — trust เท่าเดิม)
14056
+ // ✅ ปลอดภัย: data แสดงผ่าน {{ }} (Vue escape เป็น text เสมอ)
14057
+
14058
+ // ❌ ห้าม: เอา content จาก user input / URL / external API
14059
+ // → รัน JS ได้เต็มที่ (อ่าน cookie, ส่งข้อมูลออก)
14060
+ // ❌ ระวัง: v-html="untrustedData" → XSS (ใช้ {{ }} แทนถ้าได้)
14061
+
14062
+ // กฎจำง่าย:
14063
+ // 1) content รับจาก dev/schema เท่านั้น
14064
+ // 2) ที่เก็บ content ต้อง write ได้เฉพาะ dev (permission guard)</code></pre>
14065
+ </div>
14066
+ </div>
14067
+
14068
+ </div>
14069
+ </section>
14070
+
13676
14071
  <section class="section" id="field-functions">
13677
14072
  <h2>Field Functions <span class="count">this.*</span></h2>
13678
14073
  <div class="fn-list">
@@ -14640,25 +15035,27 @@ const total = rows.reduce((s, r) =&gt; s + r.amount, 0)</code></pre>
14640
15035
  </tr>
14641
15036
  <tr>
14642
15037
  <td class="opt-key">options</td>
14643
- <td class="param-type">{width?,fullscreen?,readonly?}</td>
15038
+ <td class="param-type">object</td>
14644
15039
  <td class="param-opt"><span class="opt-badge">optional</span></td>
14645
- <td class="opt-hint">Dialog options</td>
15040
+ <td class="opt-hint">Popup options: params, backdrop, readonly, annotated, callbacks &mdash; see <a href="#popup-form-guide" style="color:#cba6f7">Popup Form Guide</a></td>
14646
15041
  </tr>
14647
15042
  </table>
14648
15043
 
14649
15044
  <div class="fn-example">
14650
15045
  <div class="example-label">Example</div>
14651
15046
  <pre><code>// Open new record
14652
- this.getFormRef().openForm('form_abc123')
15047
+ this.getFormRef().openForm('6a2f90e949fcdf666f694b60')
14653
15048
 
14654
- // Open existing record
14655
- this.getFormRef().openForm('form_abc123', dataId)
15049
+ // Open existing record (edit mode)
15050
+ this.getFormRef().openForm('6a2f90e949fcdf666f694b60', dataId)
14656
15051
 
14657
15052
  // Open with pre-filled data
14658
- this.getFormRef().openForm('form_abc123', null, null, { dept: 'IT' })
15053
+ this.getFormRef().openForm('6a2f90e949fcdf666f694b60', null, null, { dept: 'IT' })
14659
15054
 
14660
- // Read-only fullscreen
14661
- this.getFormRef().openForm('form_abc123', dataId, null, null, { readonly: true, fullscreen: true })</code></pre>
15055
+ // Read-only (popup width comes from the target form's popup_size, not options)
15056
+ this.getFormRef().openForm('6a2f90e949fcdf666f694b60', dataId, null, null, { readonly: true })
15057
+
15058
+ // → Full options &amp; step-by-step examples: see "Popup Form Guide" below</code></pre>
14662
15059
  </div>
14663
15060
  </div>
14664
15061
  <div class="fn-item">
@@ -14894,6 +15291,172 @@ this.getFormRef().subFormOpen('items', 2)</code></pre>
14894
15291
  </div>
14895
15292
  </div>
14896
15293
  </section>
15294
+ <section class="section" id="popup-form-guide">
15295
+ <h2>Popup Form Guide <span class="count">openForm() &mdash; ง่าย → full options</span></h2>
15296
+ <p style="color:#bac2de;font-size:13px;margin:-4px 0 14px;line-height:1.6;">เปิดอีกฟอร์มขึ้นมาเป็น dialog ด้วย <code>this.getFormRef().openForm(formId, dataId, parentId, initData, options)</code> &mdash; ไล่จากใช้งานพื้นฐานไปจนครบทุก option (ตัวอย่างใช้ form id <code>6a2f90e949fcdf666f694b60</code>)</p>
15297
+
15298
+ <div class="fn-list">
15299
+
15300
+ <div class="fn-item">
15301
+ <div class="fn-head">
15302
+ <span class="fn-name">Signature</span>
15303
+ <span class="fn-returns">→ void</span>
15304
+ </div>
15305
+ <div class="fn-desc">4 อาร์กิวเมนต์แรกคือ shortcut ของ option ที่ใช้บ่อย ส่วนตัวที่ 5 (<code>options</code>) ใส่ key ที่เหลือได้ทั้งหมด</div>
15306
+ <div class="fn-example">
15307
+ <div class="example-label">Signature</div>
15308
+ <pre><code>openForm(formId, dataId, parentId, initData, options)
15309
+ // ─────── ────── ──────── ──────── ───────────────────────────
15310
+ // required edit child prefill { params, backdrop, readonly,
15311
+ // annotated, cancelCallback,
15312
+ // afterSaveCallback,
15313
+ // beforeSaveCallback, fixApiUrl }</code></pre>
15314
+ </div>
15315
+ </div>
15316
+
15317
+ <div class="fn-item">
15318
+ <div class="fn-head"><span class="fn-name">Level 1 · สร้างใหม่</span><span class="fn-returns">basic</span></div>
15319
+ <div class="fn-desc">เปิดฟอร์มเปล่าเพื่อสร้าง record ใหม่ &mdash; ใส่แค่ formId</div>
15320
+ <div class="fn-example"><div class="example-label">Example</div>
15321
+ <pre><code>await this.getFormRef().openForm('6a2f90e949fcdf666f694b60')</code></pre>
15322
+ </div>
15323
+ </div>
15324
+
15325
+ <div class="fn-item">
15326
+ <div class="fn-head"><span class="fn-name">Level 2 · แก้ไข record เดิม</span><span class="fn-returns">edit</span></div>
15327
+ <div class="fn-desc">ส่ง dataId (ค่า _id ของ record) เป็นอาร์กิวเมนต์ที่ 2 → ฟอร์มจะโหลดข้อมูลขึ้นมาแก้</div>
15328
+ <div class="fn-example"><div class="example-label">Example</div>
15329
+ <pre><code>const id = this.getValue() // หรือ data.order_id
15330
+ await this.getFormRef().openForm('6a2f90e949fcdf666f694b60', id)</code></pre>
15331
+ </div>
15332
+ </div>
15333
+
15334
+ <div class="fn-item">
15335
+ <div class="fn-head"><span class="fn-name">Level 3 · pre-fill ค่าเริ่มต้น</span><span class="fn-returns">initData</span></div>
15336
+ <div class="fn-desc">สร้างใหม่ (dataId = null) แต่เติมค่าตั้งต้นให้ฟอร์มผ่านอาร์กิวเมนต์ที่ 4 (initData)</div>
15337
+ <div class="fn-example"><div class="example-label">Example</div>
15338
+ <pre><code>await this.getFormRef().openForm(
15339
+ '6a2f90e949fcdf666f694b60',
15340
+ null, // dataId = null → สร้างใหม่
15341
+ null, // parentId
15342
+ { // initData → เติมค่าลงฟอร์ม
15343
+ customer_name: data.customer_name,
15344
+ order_date: new Date(),
15345
+ status: 'draft',
15346
+ }
15347
+ )</code></pre>
15348
+ </div>
15349
+ </div>
15350
+
15351
+ <div class="fn-item">
15352
+ <div class="fn-head"><span class="fn-name">Level 4 · เปิดเป็น child</span><span class="fn-returns">parentId</span></div>
15353
+ <div class="fn-desc">ผูก record ใหม่กับ record ปัจจุบันด้วย parentId (อาร์กิวเมนต์ที่ 3) &mdash; ฟอร์มปลายทางอ่านผ่าน parentId / parentData</div>
15354
+ <div class="fn-example"><div class="example-label">Example</div>
15355
+ <pre><code>await this.getFormRef().openForm(
15356
+ '6a2f90e949fcdf666f694b60',
15357
+ null,
15358
+ dataId, // parentId = record ปัจจุบัน
15359
+ { ref_no: data.doc_no }
15360
+ )</code></pre>
15361
+ </div>
15362
+ </div>
15363
+
15364
+ <div class="fn-item">
15365
+ <div class="fn-head"><span class="fn-name">Level 5 · ส่ง params</span><span class="fn-returns">options.params</span></div>
15366
+ <div class="fn-desc">ส่งค่าผ่าน <code>params</code> ในตัวที่ 5 &mdash; ต่างจาก initData ตรงที่ params เป็น read-only context (ไม่ผูกกับ field) ฟอร์มปลายทางอ่านผ่าน <code>formParams</code></div>
15367
+ <div class="fn-example"><div class="example-label">Example</div>
15368
+ <pre><code>await this.getFormRef().openForm(
15369
+ '6a2f90e949fcdf666f694b60',
15370
+ null, null, {},
15371
+ {
15372
+ params: { // อ่านที่ฟอร์มปลายทางผ่าน formParams
15373
+ from_form: formId,
15374
+ mode: 'review',
15375
+ },
15376
+ }
15377
+ )
15378
+ // ฟอร์มปลายทาง: formParams.from_form / formParams.mode</code></pre>
15379
+ </div>
15380
+ </div>
15381
+
15382
+ <div class="fn-item">
15383
+ <div class="fn-head"><span class="fn-name">Level 6 · callbacks</span><span class="fn-returns">before / after save</span></div>
15384
+ <div class="fn-desc">รับ hook ก่อน/หลัง save ของฟอร์มลูก &mdash; เหมาะกับ refresh datagrid หรือคำนวณต่อหลังบันทึก</div>
15385
+ <div class="fn-example"><div class="example-label">Example</div>
15386
+ <pre><code>await this.getFormRef().openForm(
15387
+ '6a2f90e949fcdf666f694b60',
15388
+ null, null, {},
15389
+ {
15390
+ // ก่อน save: รับ formData, return object ที่จะ merge เป็น initData
15391
+ beforeSaveCallback: (formData) => {
15392
+ return { created_by: userInfo.username }
15393
+ },
15394
+
15395
+ // หลัง save สำเร็จ: (data = record ที่บันทึก, autoSave = boolean)
15396
+ afterSaveCallback: (data, autoSave) => {
15397
+ this.reloadOptionData('order_items') // refresh datagrid
15398
+ },
15399
+ }
15400
+ )</code></pre>
15401
+ </div>
15402
+ </div>
15403
+
15404
+ <div class="fn-item">
15405
+ <div class="fn-head"><span class="fn-name">Full options</span><span class="fn-returns">ครบทุก key</span></div>
15406
+ <div class="fn-desc">ตัวอย่างใช้ทุก option พร้อมกัน</div>
15407
+ <div class="fn-example"><div class="example-label">Example</div>
15408
+ <pre><code>await this.getFormRef().openForm(
15409
+ '6a2f90e949fcdf666f694b60', // formId (required)
15410
+ null, // dataId (null = สร้างใหม่)
15411
+ dataId, // parentId
15412
+ { // initData
15413
+ customer_name: data.customer_name,
15414
+ status: 'draft',
15415
+ },
15416
+ { // options (arg 5)
15417
+ params: { from_form: formId, ref_doc: data.doc_no },
15418
+ backdrop: true, // คลิกฉากหลังเพื่อปิดได้
15419
+ readonly: false, // เปิดแบบอ่านอย่างเดียว
15420
+ annotated: false, // โหมด annotated
15421
+ fixApiUrl: '', // override api url (ปกติเว้นว่าง)
15422
+ beforeSaveCallback: (formData) => ({ created_by: userInfo.username }),
15423
+ afterSaveCallback: (data, autoSave) => {
15424
+ this.reloadOptionData('order_items')
15425
+ },
15426
+ // cancelCallback: () => { ... } // ดูข้อควรระวังด้านล่าง
15427
+ }
15428
+ )</code></pre>
15429
+ </div>
15430
+ </div>
15431
+
15432
+ <div class="fn-item">
15433
+ <div class="fn-head"><span class="fn-name">options reference</span><span class="fn-returns">arg 5</span></div>
15434
+ <div class="fn-desc">key ทั้งหมดที่ใส่ใน object ตัวที่ 5 ได้</div>
15435
+ <table class="fn-params">
15436
+ <tr><th>Key</th><th>Type</th><th>Default</th><th>Description</th></tr>
15437
+ <tr><td class="opt-key">params</td><td class="param-type">object</td><td class="opt-hint">formParams</td><td class="opt-hint">read-only context ส่งเข้าฟอร์มปลายทาง อ่านผ่าน formParams (ทับ params เดิมทั้งก้อน)</td></tr>
15438
+ <tr><td class="opt-key">backdrop</td><td class="param-type">boolean</td><td class="opt-hint">false</td><td class="opt-hint">คลิกฉากหลัง (close-on-click-modal) เพื่อปิด popup</td></tr>
15439
+ <tr><td class="opt-key">readonly</td><td class="param-type">boolean</td><td class="opt-hint">false</td><td class="opt-hint">เปิดฟอร์มแบบอ่านอย่างเดียว</td></tr>
15440
+ <tr><td class="opt-key">annotated</td><td class="param-type">boolean</td><td class="opt-hint">false</td><td class="opt-hint">โหมด annotated</td></tr>
15441
+ <tr><td class="opt-key">beforeSaveCallback</td><td class="param-type">(formData) ⇒ object</td><td class="opt-hint">—</td><td class="opt-hint">เรียกก่อน save, return ค่าที่ merge เป็น initData</td></tr>
15442
+ <tr><td class="opt-key">afterSaveCallback</td><td class="param-type">(data, autoSave) ⇒ void</td><td class="opt-hint">—</td><td class="opt-hint">เรียกหลัง save สำเร็จ</td></tr>
15443
+ <tr><td class="opt-key">cancelCallback</td><td class="param-type">() ⇒ void</td><td class="opt-hint">subFormClose</td><td class="opt-hint">เรียกตอนปิด/ยกเลิก &mdash; ดูข้อควรระวัง</td></tr>
15444
+ <tr><td class="opt-key">fixApiUrl</td><td class="param-type">string</td><td class="opt-hint">''</td><td class="opt-hint">override api url ของฟอร์มปลายทาง</td></tr>
15445
+ </table>
15446
+ </div>
15447
+
15448
+ <div class="fn-item">
15449
+ <div class="fn-head"><span class="fn-name">⚠ ข้อควรระวัง</span><span class="fn-returns">gotchas</span></div>
15450
+ <div class="fn-desc" style="line-height:1.8;">
15451
+ <strong style="color:#f9e2af;">ไม่มี width / fullscreen ใน options</strong> &mdash; ความกว้าง popup มาจาก <code>form_options.popup_size</code> ของ <em>ฟอร์มปลายทาง</em> (ตั้งใน builder) ไม่ได้กำหนดผ่าน openForm<br>
15452
+ <strong style="color:#f9e2af;">cancelCallback ทับตัว default</strong> &mdash; default คือ <code>subFormClose</code> ที่ปิด popup ให้ ถ้าส่งเอง popup จะไม่ปิดอัตโนมัติ ต้องเรียกปิดเองในฟังก์ชัน<br>
15453
+ <strong style="color:#f9e2af;">params ทับทั้งก้อน</strong> &mdash; default = formParams ของฟอร์มปัจจุบัน ถ้าส่ง params เอง ค่าเดิมจะหายทั้งหมด<br>
15454
+ <strong style="color:#f9e2af;">openForm vs subFormOpen</strong> &mdash; <code>openForm</code> เปิด<em>อีกฟอร์ม</em>ทั้งฟอร์ม ส่วน <code>subFormOpen(name, rowIndex)</code> เปิด row ของ sub-form field ในฟอร์มเดียวกัน
15455
+ </div>
15456
+ </div>
15457
+
15458
+ </div>
15459
+ </section>
14897
15460
  <section class="section" id="api-functions">
14898
15461
  <h2>API Functions <span class="count">this.getFormRef().userState.*</span></h2>
14899
15462
  <div class="fn-list">