rip-lang 3.10.7 → 3.10.9

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.
Binary file
@@ -7,7 +7,7 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,400;0,500;0,700;1,400&family=Nothing+You+Could+Do&display=swap" rel="stylesheet">
10
- <script type="module" src="../dist/rip-ui.min.js"></script>
10
+ <script type="module" src="../dist/rip-ui.min.js" data-hash="true"></script>
11
11
  <style>
12
12
  /* ==========================================================================
13
13
  Lab Results — Styles
@@ -434,6 +434,7 @@ body {
434
434
  color: rgba(255,255,255,.85);
435
435
  margin-top: 20px;
436
436
  line-height: 1.5;
437
+ white-space: pre-line;
437
438
  }
438
439
 
439
440
  .detail__content {
@@ -500,6 +501,18 @@ body {
500
501
  color: rgba(51,51,51,.5);
501
502
  }
502
503
 
504
+ .detail__maintenance-icon + div {
505
+ max-width: 80%;
506
+ }
507
+
508
+ .detail__footer-image {
509
+ flex: 1;
510
+ min-height: 280px;
511
+ background-size: cover;
512
+ background-repeat: no-repeat;
513
+ background-position: center;
514
+ }
515
+
503
516
  .gauge {
504
517
  position: relative;
505
518
  display: inline-block;
@@ -604,6 +617,8 @@ export Home = component
604
617
  cholesterolHdlRatio: 4.3
605
618
  glucose: 75
606
619
  a1c: 5.2
620
+ waistCircumference: 36
621
+ bloodPressure: '118/76'
607
622
  ]
608
623
 
609
624
  ranges :=
@@ -706,7 +721,9 @@ export Home = component
706
721
  drawerOpen = false
707
722
  mq.addEventListener 'change', handler
708
723
 
709
- ~> @setupResize()
724
+ ~>
725
+ @setupResize()
726
+ window.app = { history, ranges, firstName, lastName, age, gender }
710
727
 
711
728
  render
712
729
  .app
@@ -908,28 +925,18 @@ export Summary = component
908
925
  s = []
909
926
  s.push { label: 'Heart Health', keys: ['triglycerides', 'hdlCholesterol', 'totalCholesterol', 'cholesterolHdlRatio'] }
910
927
  s.push { label: 'Pancreas Health', keys: ['glucose', 'a1c'] }
911
- if history and history[0] and history[0].waistCircumference?
912
- s.push { label: 'Physical Measurements', keys: ['waistCircumference', 'bloodPressure'] }
928
+ s.push { label: 'Physical Measurements', keys: ['waistCircumference', 'bloodPressure'] } if history?[0]?.waistCircumference
913
929
  s
914
930
 
915
- rows ~=
916
- result = []
917
- for section in sections
918
- for k in section.keys
919
- ref = ranges[k]
920
- continue unless ref
921
- val = history[0]?[k]
922
- continue unless val?
923
- status = acceptable(k, val)
924
- result.push
925
- section: section.label
926
- key: k
927
- name: ref.name
928
- desc: ref.desc
929
- value: String(val)
930
- rowClass: if status is 'acceptable' then 'row-acceptable' else 'row-unacceptable'
931
- icon: if status is 'acceptable' then checkIcon else warnIcon
932
- result
931
+ rowClass: (k) ->
932
+ val = history[0]?[k]
933
+ return '' unless val?
934
+ if acceptable(k, val) is 'acceptable' then 'row-acceptable' else 'row-unacceptable'
935
+
936
+ rowIcon: (k) ->
937
+ val = history[0]?[k]
938
+ return '' unless val?
939
+ if acceptable(k, val) is 'acceptable' then checkIcon else warnIcon
933
940
 
934
941
  render
935
942
  .('brochure__page page-summary')
@@ -949,22 +956,27 @@ export Summary = component
949
956
 
950
957
  .summary__copy "This report serves as an easy reference to review all of your testing results, including data from previous years. We encourage you to use this information in conjunction with an exam by your doctor, not as a replacement for one. We hope this summary will be a good starting point for conversations with your doctor about improving your overall health."
951
958
 
952
- table.summary-table
953
- thead
954
- tr
955
- th ""
956
- th historyDate
957
- tbody
958
- for row in rows
959
+ for section, i in sections
960
+ table.summary-table
961
+ unless i
962
+ thead
963
+ tr
964
+ th
965
+ th historyDate
966
+ tbody
959
967
  tr
960
- class: row.rowClass
968
+ td section.label
961
969
  td
962
- .status-icon
963
- innerHTML: row.icon
964
- div
965
- .name row.name
966
- .desc row.desc
967
- td.value row.value
970
+ for k in section.keys
971
+ tr
972
+ class: @rowClass(k)
973
+ td
974
+ .status-icon
975
+ innerHTML: @rowIcon(k)
976
+ div
977
+ .name ranges[k].name
978
+ .desc ranges[k].desc
979
+ td.value history[0]?[k]
968
980
  </script>
969
981
 
970
982
  <script type="text/rip" data-name="detail-page">
@@ -978,14 +990,14 @@ export DetailPage = component
978
990
 
979
991
  shouldShow ~=
980
992
  return true unless topic.conditional
981
- history and history[0] and history[0][topic.conditional]?
993
+ history?[0]?[topic.conditional]?
982
994
 
983
995
  tipIcons :=
984
996
  utensils: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 2v7c0 1.1.9 2 2 2h4c1.1 0 2-.9 2-2V2"/><line x1="7" y1="2" x2="7" y2="22"/><path d="M21 15V2a5 5 0 00-5 5v6h4"/><line x1="21" y1="12" x2="21" y2="22"/></svg>'
985
- drop: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2.69l5.66 5.66a8 8 0 11-11.31 0z"/></svg>'
986
- walk: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="13" cy="4" r="2"/><path d="M7 21l3-4V11l-2-2-3 3"/><path d="M16 21l-2-6-3-1"/></svg>'
987
- doctor: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 11V3M4.93 7.5l7.07 3.5 7.07-3.5"/><circle cx="12" cy="18" r="3"/><line x1="12" y1="15" x2="12" y2="11"/></svg>'
988
- smile: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>'
997
+ drop: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2.69l5.66 5.66a8 8 0 11-11.31 0z"/></svg>'
998
+ walk: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="13" cy="4" r="2"/><path d="M7 21l3-4V11l-2-2-3 3"/><path d="M16 21l-2-6-3-1"/></svg>'
999
+ doctor: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 11V3M4.93 7.5l7.07 3.5 7.07-3.5"/><circle cx="12" cy="18" r="3"/><line x1="12" y1="15" x2="12" y2="11"/></svg>'
1000
+ smile: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>'
989
1001
 
990
1002
  checkIcon := '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>'
991
1003
  warnIcon := '<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>'
@@ -1008,9 +1020,6 @@ export DetailPage = component
1008
1020
  .detail__header-title topic.title
1009
1021
  .detail__header-copy topic.copy
1010
1022
 
1011
- if topic.isImagePage
1012
- .detail__header style: "background-image: url(#{topic.image}); background-size: cover"
1013
-
1014
1023
  .detail__content
1015
1024
  for bio in topic.biomarkers
1016
1025
  .detail__item
@@ -1025,6 +1034,7 @@ export DetailPage = component
1025
1034
  .desc ranges[bio.key].desc
1026
1035
 
1027
1036
  .detail__item-info
1037
+ p ranges[bio.key].info[@statusOf(bio.key)]
1028
1038
  p innerHTML: bio.text
1029
1039
  if bio.extra
1030
1040
  p bio.extra
@@ -1036,6 +1046,9 @@ export DetailPage = component
1036
1046
  .detail__maintenance-icon
1037
1047
  innerHTML: (tipIcons[tip.icon] or '')
1038
1048
  div tip.text
1049
+
1050
+ if topic.isImagePage
1051
+ .detail__footer-image style: "background-image: url(#{topic.image})"
1039
1052
  </script>
1040
1053
 
1041
1054
  <script type="text/rip" data-name="gauge">
@@ -1106,12 +1119,5 @@ export Gauge = component
1106
1119
  .gauge__units units
1107
1120
  </script>
1108
1121
 
1109
- <!-- ===== Boot ===== -->
1110
-
1111
- <script type="text/rip">
1112
- { launch } = importRip! '/rip/ui.rip'
1113
- launch hash: true
1114
- </script>
1115
-
1116
1122
  </body>
1117
1123
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rip-lang",
3
- "version": "3.10.7",
3
+ "version": "3.10.9",
4
4
  "description": "A modern language that compiles to JavaScript",
5
5
  "type": "module",
6
6
  "main": "src/compiler.js",
package/src/browser.js CHANGED
@@ -136,12 +136,27 @@ if (typeof globalThis !== 'undefined') {
136
136
  globalThis.__ripExports = { compile, compileToJS, formatSExpr, VERSION, BUILD_DATE, getReactiveRuntime, getComponentRuntime };
137
137
  }
138
138
 
139
- // Auto-process <script type="text/rip"> blocks.
139
+ // Auto-launch: if rip-ui is bundled and the page has component scripts or a data-url,
140
+ // call launch() automatically with config from the script tag's data attributes.
141
+ async function autoLaunch() {
142
+ if (globalThis.__ripLaunched) return;
143
+ const ui = importRip.modules?.['ui.rip'];
144
+ if (!ui?.launch) return;
145
+ const cfg = document.querySelector('script[data-hash], script[data-url]');
146
+ const url = cfg?.getAttribute('data-url') || '';
147
+ const hasComponents = document.querySelectorAll('script[type="text/rip"][data-name]').length > 0;
148
+ if (!hasComponents && !url) return;
149
+ const opts = {};
150
+ if (cfg?.hasAttribute('data-hash')) opts.hash = cfg.getAttribute('data-hash') !== 'false';
151
+ await ui.launch(url, opts);
152
+ }
153
+
154
+ // Auto-process <script type="text/rip"> blocks, then auto-launch if applicable.
140
155
  // Deferred via queueMicrotask so bundled entry code (e.g. rip-ui.min.js registering
141
156
  // importRip.modules) runs before script processing begins.
142
157
  if (typeof document !== 'undefined') {
143
158
  globalThis.__ripScriptsReady = new Promise(resolve => {
144
- const run = () => processRipScripts().then(resolve);
159
+ const run = () => processRipScripts().then(autoLaunch).then(resolve);
145
160
  if (document.readyState === 'loading') {
146
161
  document.addEventListener('DOMContentLoaded', () => queueMicrotask(run));
147
162
  } else {
package/src/components.js CHANGED
@@ -492,7 +492,8 @@ export function installComponentSupport(CodeGenerator, Lexer) {
492
492
  }
493
493
  current = current[1];
494
494
  }
495
- let raw = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() : 'div');
495
+ let raw = typeof current === 'string' ? current : (current instanceof String ? current.valueOf() : null);
496
+ if (raw === null) return { tag: null, classes, id: undefined };
496
497
  // Split tag#id — e.g. "div#content" → tag: "div", id: "content"
497
498
  let [tag, id] = raw.split('#');
498
499
  if (!tag) tag = 'div'; // bare #id → div
@@ -1029,17 +1030,39 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1029
1030
  if (id) {
1030
1031
  this._createLines.push(`${elVar}.id = '${id}';`);
1031
1032
  }
1033
+
1034
+ // Defer class emission when selector classes exist so class: attributes merge
1035
+ const prevClassArgs = this._pendingClassArgs;
1036
+ const prevClassEl = this._pendingClassEl;
1032
1037
  if (classes.length > 0) {
1033
- if (isSvg) {
1034
- this._createLines.push(`${elVar}.setAttribute('class', '${classes.join(' ')}');`);
1035
- } else {
1036
- this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
1037
- }
1038
+ this._pendingClassArgs = [`'${classes.join(' ')}'`];
1039
+ this._pendingClassEl = elVar;
1038
1040
  }
1039
1041
 
1040
1042
  if (tag === 'svg') this._svgDepth = (this._svgDepth || 0) + 1;
1041
1043
  this.appendChildren(elVar, args);
1042
1044
  if (tag === 'svg') this._svgDepth--;
1045
+
1046
+ // Emit final class: if only selector classes (no dynamic additions), set statically
1047
+ if (classes.length > 0) {
1048
+ if (this._pendingClassArgs.length === 1) {
1049
+ if (isSvg) {
1050
+ this._createLines.push(`${elVar}.setAttribute('class', '${classes.join(' ')}');`);
1051
+ } else {
1052
+ this._createLines.push(`${elVar}.className = '${classes.join(' ')}';`);
1053
+ }
1054
+ } else {
1055
+ const combined = this._pendingClassArgs.join(', ');
1056
+ if (isSvg) {
1057
+ this._setupLines.push(`__effect(() => { ${elVar}.setAttribute('class', __clsx(${combined})); });`);
1058
+ } else {
1059
+ this._setupLines.push(`__effect(() => { ${elVar}.className = __clsx(${combined}); });`);
1060
+ }
1061
+ }
1062
+ this._pendingClassArgs = prevClassArgs;
1063
+ this._pendingClassEl = prevClassEl;
1064
+ }
1065
+
1043
1066
  return elVar;
1044
1067
  };
1045
1068
 
@@ -1186,7 +1209,13 @@ export function installComponentSupport(CodeGenerator, Lexer) {
1186
1209
  continue;
1187
1210
  }
1188
1211
 
1189
- if (BOOLEAN_ATTRS.has(key)) {
1212
+ if (key === 'innerHTML' || key === 'textContent' || key === 'innerText') {
1213
+ if (this.hasReactiveDeps(value)) {
1214
+ this._setupLines.push(`__effect(() => { ${elVar}.${key} = ${valueCode}; });`);
1215
+ } else {
1216
+ this._createLines.push(`${elVar}.${key} = ${valueCode};`);
1217
+ }
1218
+ } else if (BOOLEAN_ATTRS.has(key)) {
1190
1219
  if (this.hasReactiveDeps(value)) {
1191
1220
  this._setupLines.push(`__effect(() => { ${elVar}.toggleAttribute('${key}', !!${valueCode}); });`);
1192
1221
  } else {
@@ -73,7 +73,7 @@ grammar =
73
73
  o 'ReactiveAssign'
74
74
  o 'ComputedAssign'
75
75
  o 'ReadonlyAssign'
76
- o 'ReactAssign'
76
+ o 'Effect'
77
77
  o 'If'
78
78
  o 'Try'
79
79
  o 'While'
@@ -208,13 +208,13 @@ grammar =
208
208
  ]
209
209
 
210
210
  # Reactive effect (~>) — side effects that run when dependencies change
211
- ReactAssign: [
212
- o 'Assignable REACT_ASSIGN Expression' , '["effect", 1, 3]'
213
- o 'Assignable REACT_ASSIGN TERMINATOR Expression' , '["effect", 1, 4]'
214
- o 'Assignable REACT_ASSIGN INDENT Expression OUTDENT', '["effect", 1, 4]'
215
- o 'REACT_ASSIGN Expression' , '["effect", null, 2]'
216
- o 'REACT_ASSIGN TERMINATOR Expression' , '["effect", null, 3]'
217
- o 'REACT_ASSIGN INDENT Expression OUTDENT' , '["effect", null, 3]'
211
+ Effect: [
212
+ o 'Assignable EFFECT Expression' , '["effect", 1, 3]'
213
+ o 'Assignable EFFECT TERMINATOR Expression' , '["effect", 1, 4]'
214
+ o 'Assignable EFFECT Block' , '["effect", 1, 3]'
215
+ o 'EFFECT Expression' , '["effect", null, 2]'
216
+ o 'EFFECT TERMINATOR Expression' , '["effect", null, 3]'
217
+ o 'EFFECT Block' , '["effect", null, 2]'
218
218
  ]
219
219
 
220
220
  # ============================================================================
@@ -802,7 +802,7 @@ grammar =
802
802
  o 'EXPORT ReactiveAssign' , '["export", 2]'
803
803
  o 'EXPORT ComputedAssign' , '["export", 2]'
804
804
  o 'EXPORT ReadonlyAssign' , '["export", 2]'
805
- o 'EXPORT ReactAssign' , '["export", 2]'
805
+ o 'EXPORT Effect' , '["export", 2]'
806
806
  o 'EXPORT DEFAULT Expression' , '["export-default", 3]'
807
807
  o 'EXPORT DEFAULT INDENT Object OUTDENT' , '["export-default", 4]'
808
808
  o 'EXPORT EXPORT_ALL FROM String' , '["export-all", 4]'
@@ -437,7 +437,7 @@ export install = (Generator) ->
437
437
  # Statement tokens handled by parseUnary (for 'break if done', 'return x unless err')
438
438
  @_exprHandledTokens.add 'STATEMENT'
439
439
  @_exprHandledTokens.add 'RETURN'
440
- @_exprHandledTokens.add 'REACT_ASSIGN'
440
+ @_exprHandledTokens.add 'EFFECT'
441
441
 
442
442
  Generator::_findKeywordTokens = (type, dispatchName, handledTokens, visited) ->
443
443
  return if visited.has type.name
@@ -2271,7 +2271,7 @@ export install = (Generator) ->
2271
2271
  lines.push " if (token === 'STATEMENT') { const v = tokenText; advance(); return v; }"
2272
2272
  lines.push " if (token === 'RETURN') return parseReturn();"
2273
2273
  # Fire-and-forget effect (~> expr) — prefix form without left-hand side
2274
- lines.push " if (token === 'REACT_ASSIGN') return parseReactAssign();"
2274
+ lines.push " if (token === 'EFFECT') return parseEffect();"
2275
2275
  lines.push " throw new Error('Parse error: unexpected token ' + token + ' at line ' + ((tokenLoc && tokenLoc.r || 0) + 1));"
2276
2276
  lines.push "}"
2277
2277
  lines.join '\n'
package/src/lexer.js CHANGED
@@ -1156,7 +1156,7 @@ export class Lexer {
1156
1156
  else if (val === '~=') tag = 'COMPUTED_ASSIGN';
1157
1157
  else if (val === ':=') tag = 'REACTIVE_ASSIGN';
1158
1158
  else if (val === '<=>') tag = 'BIND';
1159
- else if (val === '~>') tag = 'REACT_ASSIGN';
1159
+ else if (val === '~>') tag = 'EFFECT';
1160
1160
  else if (val === '=!') tag = 'READONLY_ASSIGN';
1161
1161
  // Merge assignment: *config = {a: 1} → Object.assign(config, {a: 1})
1162
1162
  // Also supports *@ = props → Object.assign(this, props)